Android实现一个可拖拽带有坐标尺的进度条

简介: Canvas绘制这样的一个可拖拽坐标尺,基本上可以拆分出四部分,第一部分就是背景和默认的离散间隔,第二部分是移动的背景和离散间隔,第三部分是移动的图片也就是thumb,最后一部分是底部的文字坐标。


拿到上边的UI效果图,给我的第一印象就是这实现起来也太简单了吧,SeekBar轻轻松松就搞定了,换个thumb,加个渐变不就完成了,说搞就搞,搞着搞着就抑郁了,底部坐标尺还能搞,等比例分割后,在SeekBar下面多设置几个TextView就行了,中间的等比例小分割线怎么搞?而且滑动前滑动后都需要有,并且,左右的分割线还要留出一小段间距,渐变颜色要跟着滑动的距离进行展示,而不是整个宽度展示,在多种条件下,SeekBar就很难满足这个需求了,怎么办?只能自定义了。


还是按照惯例,粗略的列一个大纲:


1、分析要素,确定实现方案

2、主要代码进行刨析

3、开源地址及使用方式

4、总结


一、分析要素,确定实现方案


Canvas绘制这样的一个可拖拽坐标尺,基本上可以拆分出四部分,第一部分就是背景和默认的离散间隔,第二部分是移动的背景和离散间隔,第三部分是移动的图片也就是thumb,最后一部分是底部的文字坐标。


四部分基本上就绘制出来了,但是除了绘制之外,还需要考虑一下其他的因素,比如高度,比如手指的移动事件等。


1、设置默认高度


设置默认高度的原因,是为了让View更好的展示一个合适的尺寸,不至于设置wrap_content时不展示,具体的设置可以根据当前设置的模式来控制,关于模式呢,有三种,这个在之前的文章中介绍过,这里就不详细介绍了,当控件设置wrap_content时,此时的模式为MeasureSpec.AT_MOST,在这个模式下,我们就要给一个默认的高度。


overridefunonMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
valheightMode=MeasureSpec.getMode(heightMeasureSpec)
varwindowHeight=heightMeasureSpecif (heightMode==MeasureSpec.AT_MOST) {
windowHeight=mDefaultHeight.toInt()//默认的高度        }
setMeasuredDimension(widthMeasureSpec, windowHeight)
    }


2、拖动事件


实现拖动效果,我们就需要监听用户的手指移动事件了,也就是在自定义View中我们要重写onTouchEvent方法,在这个方法里,需要针对手指的按下、抬起、移动做相应的处理。


在onTouchEvent里我做了如下处理,一是直接返回,不执行事件的消费,目的是让自定义View可实现静态展示和动态展示两种效果,通过一个变量mProgressIsIntercept来控制;第二个是解决与父View的滑动冲突事件,在有横向或者纵向滑动事件时,在拖动的时候,难免会有冲突,那么就需要通知父View不要消费事件,也就是执行requestDisallowInterceptTouchEvent方法。


所有的拖拽效果,都是在move事件,不断的改变坐标执行更新UI的方式实现的,mMoveProgress就是手指移动的坐标。


onTouchEvent(event: MotionEvent?): Boolean {
super.onTouchEvent(event)
//如果为true直接返回,不进行拖拽if (mProgressIsIntercept) {
returnmProgressIsIntercept        }
when (event?.action) {
MotionEvent.ACTION_DOWN-> {
parent.requestDisallowInterceptTouchEvent(mDisallowIntercept)
valdownX=getChangeX(event.x)
valstartX=mMoveOldX-mProgressMarginLeftRightvalendX=mMoveOldX+mProgressMarginLeftRightreturndownXinstartX..endX            }
MotionEvent.ACTION_MOVE-> {
//移动varmoveX=getChangeX(event.x)
//滑动至最右边//计算最后边的坐标valviewWidth=getViewWidth()
if (moveX>=viewWidth) {
moveX=viewWidth                }
mMoveProgress=moveXinvalidate()
            }
MotionEvent.ACTION_UP-> {
//手指谈起mMoveOldX=getChangeX(event.x)
valviewWidth=getViewWidth()
if (mMoveOldX>=viewWidth) {
mMoveOldX=viewWidth                }
            }
        }
returntrue    }


二、主要代码进行刨析


1、绘制背景


背景没什么好说的,就是一个简单的圆角矩形,使用drawRoundRect绘制即可,需要确定的是左上右下的间距。


/*** AUTHOR:AbnerMing* INTRODUCE:绘制背景*/privatefuncanvasBackground(canvas: Canvas) {
mPaint!!.color=mProgressBackgroundvalrect=RectF().apply {
left=mProgressMarginLeftRighttop=mProgressMarginTopBottomright=width.toFloat() -mProgressMarginLeftRightbottom=mProgressHeight+mProgressMarginTopBottom        }
canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mPaint!!)
    }


2、绘制离散间隔


离散间隔,需要确定,间隔数,然后根据间隔数量,动态的计算每个间隔的位置,可以使用drawLine绘制一个小小的竖线,竖线也需要确定距离上下的距离和自身的宽度;特殊情况下,离散间隔,在滑动前后的颜色是不一样的,所以这里也做了一个动态改变颜色的判断。


/*** AUTHOR:AbnerMing* INTRODUCE:绘制离散间隔*/privatefuncanvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
valrect=            (width-mProgressMarginLeftRight*2-mIntervalParentLeftRight*2) /mIntervalSizeif (isCanvas) {
mPaint!!.color=mIntervalSelectColor        } else {
mPaint!!.color=mIntervalColor        }
mPaint!!.strokeWidth=mIntervalWidthfor (ain0..mIntervalSize) {
valx= (rect*a) +mProgressMarginLeftRight+mIntervalParentLeftRightvaly=mIntervalMarginTopBottom+mProgressMarginTopBottomcanvas.drawLine(
x,
y,
x,
mProgressHeight+mProgressMarginTopBottom-mIntervalMarginTopBottom,
mPaint!!            )
        }
    }


3、绘制移动thumb


关于thumb,首先要确定的就是大小,如果设置了宽高,那么就需要使用Bitmap重新设置高度,改变thumb的坐标,只需要不断的改变图片的left坐标点即可,也就是通过上述的Move事件的中移动坐标来设置。


/*** AUTHOR:AbnerMing* INTRODUCE:绘制移动的图标*/privatefuncanvasMoveIcon(canvas: Canvas) {
mProgressThumb?.let {
vardecodeResource=BitmapFactory.decodeResource(resources, it)
mProgressThumbWidth=decodeResource.widthif (mThumbWidth!=0f) {
valheight: Int=decodeResource.height// 设置想要的大小valnewWidth=mThumbWidthvalnewHeight=mThumbHeight// 计算缩放比例valscaleWidth=newWidth/widthvalscaleHeight=newHeight/height// 取得想要缩放的matrix参数valmatrix=Matrix()
matrix.postScale(scaleWidth, scaleHeight)
// 得到新的图片decodeResource=Bitmap.createBitmap(decodeResource, 0, 0, width, height, matrix, true)
            }
varmThumpLeft=mMoveProgressif (mThumpLeft< (mProgressThumbWidth/2-mIntervalParentLeftRight+mProgressThumbSpacing)) {
mThumpLeft=mProgressThumbWidth/2-mIntervalParentLeftRight+mProgressThumbSpacing            }
if (mThumpLeft> (getViewWidth() -mIntervalParentLeftRight+mProgressThumbSpacing)) {
mThumpLeft=getViewWidth() -mIntervalParentLeftRight+mProgressThumbSpacing            }
canvas.drawBitmap(
decodeResource, mThumpLeft, mThumbMarginTop, mIconPaint!!            )
        }
    }


4、绘制移动进度


移动的进度,和背景的绘制是一样的,只不过需要按照手指的坐标一点一点的移动距离,也就是不断的改变右边的坐标值,同样的,也是通过Move事件中的mMoveProgress进度来动态的计算。进度的渐变比较简单,使用的是画笔的shader属性,当前使用的是横向的线性渐变LinearGradient。


/*** AUTHOR:AbnerMing* INTRODUCE:绘制进度*/privatefuncanvasMoveProgress(canvas: Canvas) {
//为空if (mColorArray.isEmpty()) {
mColorArray=intArrayOf(
ContextCompat.getColor(context, R.color.text_ff3e3e93),
ContextCompat.getColor(context, R.color.text_ff8548d2),
            )
        }
vallinearShader=LinearGradient(
0f,
0f,
mMoveProgress+mProgressMarginLeftRight,
mProgressHeight,
mColorArray,
floatArrayOf(0f, 1f),
Shader.TileMode.CLAMP        )
mProgressPaint!!.shader=linearShader//等于0时valrect=RectF()
rect.left=mProgressMarginLeftRightrect.top=mProgressMarginTopBottomrect.right=mMoveProgress+mProgressMarginLeftRightrect.bottom=mProgressHeight+mProgressMarginTopBottomcanvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mProgressPaint!!)
//计算比例mGraduationResult=            ((mMoveProgress/getViewWidth()) *mMaxProgress).roundToInt()//(endProgress * mMaxProgress).roundToInt()if (mGraduationResult<1) {
mGraduationResult=if (mGraduationSectionZero) {
0            } else {
1            }
        }
if (mGraduationResult>=mMaxProgress) {
mGraduationResult=mMaxProgress        }
mMoveProgressCallback?.invoke(mGraduationResult)
    }


5、绘制文字刻度


其实大家可以发现,离散间隔和底部的坐标文字刻度,其实是一一对应的,既然是相互关联,我们直接放到一起就可以,也就是在遍历离散间隔的时候,我们直接绘制底部的坐标尺刻度。

坐标刻度,有四种效果,第一种是不要刻度值,第二种是只要开始和结尾刻度值,第三种是展示所有的刻度值,第四种是刻度值是从0还是从1开始。


mIsGraduation是用于判断是否需要刻度值的变量,为true则需要绘制,否则就不绘制,也就是不需要刻度值。mHideGraduationSectionCenter为隐藏中间刻度的变量,为true隐藏,否则为不隐藏,具体的代码如下:


/*** AUTHOR:AbnerMing* INTRODUCE:绘制离散间隔*/privatefuncanvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
valrect=            (width-mProgressMarginLeftRight*2-mIntervalParentLeftRight*2) /mIntervalSizeif (isCanvas) {
mPaint!!.color=mIntervalSelectColor        } else {
mPaint!!.color=mIntervalColor        }
mPaint!!.strokeWidth=mIntervalWidthfor (ain0..mIntervalSize) {
valx= (rect*a) +mProgressMarginLeftRight+mIntervalParentLeftRightvaly=mIntervalMarginTopBottom+mProgressMarginTopBottomcanvas.drawLine(
x,
y,
x,
mProgressHeight+mProgressMarginTopBottom-mIntervalMarginTopBottom,
mPaint!!            )
//绘制刻度值if (mIsGraduation&&isCanvas) {
if (mHideGraduationSectionCenter&& (a!=0&&a!=mIntervalSize)) {
//隐藏中间continue                }
vargraduation=a*mGraduationSection//是否从0开始记录if (graduation==0&&!mGraduationSectionZero) {
graduation=1                }
//如果移动到了,改变颜色if (mGraduationResult>=graduation&&mGraduationResult<graduation+mGraduationSection) {
mGraduationPaint?.color=mGraduationSelectTextColor                } else {
mGraduationPaint?.color=mGraduationTextColor                }
valtext=graduation.toString()
valrectText=Rect()
mGraduationPaint!!.getTextBounds(text, 0, text.length, rectText)
valtextWidth=rectText.width()
valtextHeight=rectText.height()
canvas.drawText(
text,
x-textWidth/2,
mProgressHeight+mProgressMarginTopBottom*2+textHeight+mGraduationMarginTop,
mGraduationPaint!!                )
            }
        }
    }


三、开源地址及使用方式

目前已经上传到了Github,本身就一个简单的类,没多少东西,需要的铁子,可以直接查看源码即可。


地址:https://github.com/AbnerMing888/MoveProgress

如果懒得下载源码,想直接使用,没得问题,我已经上传到了远程Maven,大家可以依赖使用。


1、在你的根项目下的build.gradle文件下,引入maven。


allprojects {
repositories {
maven { url"https://gitee.com/AbnerAndroid/almighty/raw/master" }
    }
}


2、在你需要使用的Module中build.gradle文件下,引入依赖。


dependencies {
implementation'com.vip:moveprogress:1.0.0'}


3、XML引入即可


<com.vip.moveprogress.MoveProgressandroid:layout_width="match_parent"android:layout_height="wrap_content"app:ms_graduation_hide_center="true"/>


相关属性


属性

类型

概述

ms_height

dimension

View视图的高度

ms_progress_height

dimension

进度条的高度

ms_progress_thumb

reference

进度条的Icon

ms_progress_margin_top_bottom

dimension

进度条距离icon的上下距离

ms_progress_margin_left_right

dimension

进度条距离左右的边距

ms_progress_radius

dimension

进度条的圆角

ms_progress_background

color

进度条的背景颜色

ms_interval_color

color

间隔线颜色

ms_interval_select_color

color

间隔线选中颜色

ms_interval_parent_margin_left_right

dimension

间隔线距离父左右

ms_interval_size

integer

间隔线数量

ms_interval_width

dimension

间隔线宽度

ms_interval_margin_top_bottom

dimension

间隔线上下边距

ms_progress_move_color

reference

定义的移动颜色

ms_progress_max

integer

最大进度

ms_progress_default

integer

默认进度

ms_is_graduation

boolean

是否显示刻度尺

ms_graduation_text_size

dimension

刻度尺文字大小

ms_graduation_text_color

color

刻度尺文字颜色

ms_graduation_select_text_color

color

刻度尺文字选中颜色

ms_graduation_section

integer

刻度值段

ms_graduation_section_zero

boolean

刻度值段从零开始

ms_graduation_hide_center

boolean

刻度值段中间是否隐藏

ms_graduation_margin_top

dimension

刻度值距离上边的距离

ms_progress_thumb_width

dimension

icon的宽

ms_progress_thumb_height

dimension

icon的高

ms_progress_thumb_margin_top

dimension

icon距离上边的高度

ms_progress_thumb_spacing

dimension

icon的内边距

ms_progress_disallow_intercept

boolean

是否拦截

ms_progress_is_intercept

boolean

是否禁止拖拽


相关方法


方法

参数

概述

getProgress

无参

返回当前进度

changeProgress

Int

改变当前进度

getMoveProgress

返回Int

回调函数

setProgressIsIntercept

Boolean

设置是否进行拦截


四、总结


关于渐变,需要注意,渐变的范围不是默认的从左到右固定的距离,而是从左到手指滑动的距离,这一点需要注意,也就是在设置渐变的时候,终止的X坐标需要根据手势的左边动态设置。


从这个简单的拖拽进度条,我们可以了解到,canvas绘制线,圆角矩形,图片以及和手势结合的相关知识点,本身并没有难点。

相关文章
|
6月前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
124 1
|
Android开发 UED
Android 实现通知栏和进度条效果(适用于Android8.0以上)
Android 实现通知栏和进度条效果(适用于Android8.0以上)
172 0
|
6月前
|
Android开发
Android通过手势(多点)缩放和拖拽图片
Android通过手势(多点)缩放和拖拽图片
53 4
|
5月前
|
Java API Android开发
19. 【Android教程】进度条 ProgressBar
19. 【Android教程】进度条 ProgressBar
148 4
|
6月前
|
XML API Android开发
Android 自定义View 之 圆环进度条
Android 自定义View 之 圆环进度条
110 0
|
6月前
|
XML Java Android开发
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
678 1
|
Android开发
Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)
Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)
185 0
|
6月前
|
XML API Android开发
Android 自定义View 之 饼状进度条
Android 自定义View 之 饼状进度条
|
6月前
|
Android开发 UED
[Android]ProgressBar进度条
[Android]ProgressBar进度条
73 0
|
6月前
|
XML Java Android开发
Android Studio App开发中异步任务AsynTask与异步服务IntentService的讲解与实战(实现四大名著的加载进度条 附源码)
Android Studio App开发中异步任务AsynTask与异步服务IntentService的讲解与实战(实现四大名著的加载进度条 附源码)
252 0