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

拿到上边的UI作用图,给我的榜首印象便是这完成起来也太简略了吧,SeekBar轻轻松松就搞定了,换个thumb,加个突变不就完成了,说搞就搞,搞着搞着就郁闷了,底部坐标尺还能搞,等份额分割后,在SeekBar下面多设置几个TextView就行了,中心的等份额小分割线怎么搞?而且滑动前滑动后都需求有,并且,左右的分割线还要留出一小段间距,突变色彩要跟着滑动的间隔进行展现,而不是整个宽度展现,在多种条件下,SeekBar就很难满意这个需求了,怎么办?只能自定义了。

还是依照惯例,粗略的列一个纲要:

1、剖析要素,确认完成方案

2、首要代码进行刨析

3、开源地址及运用方法

4、总结

一、剖析要素,确认完成方案

Canvas制作这样的一个可拖拽坐标尺,基本上能够拆分出四部分,榜首部分便是布景和默许的离散间隔,第二部分是移动的布景和离散间隔,第三部分是移动的图片也便是thumb,最终一部分是底部的文字坐标。

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

1、设置默许高度

设置默许高度的原因,是为了让View更好的展现一个适宜的尺度,不至于设置wrap_content时不展现,详细的设置能够依据当时设置的形式来操控,关于形式呢,有三种,这个在之前的文章中介绍过,这儿就不详细介绍了,当控件设置wrap_content时,此时的形式为MeasureSpec.AT_MOST,在这个形式下,咱们就要给一个默许的高度。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        var windowHeight = heightMeasureSpec
        if (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) {
            return mProgressIsIntercept
        }
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(mDisallowIntercept)
                val downX = getChangeX(event.x)
                val startX = mMoveOldX - mProgressMarginLeftRight
                val endX = mMoveOldX + mProgressMarginLeftRight
                return downX in startX..endX
            }
            MotionEvent.ACTION_MOVE -> {
                //移动
                var moveX = getChangeX(event.x)
                //滑动至最右边
                //核算最终边的坐标
                val viewWidth = getViewWidth()
                if (moveX >= viewWidth) {
                    moveX = viewWidth
                }
                mMoveProgress = moveX
                invalidate()
            }
            MotionEvent.ACTION_UP -> {
                //手指谈起
                mMoveOldX = getChangeX(event.x)
                val viewWidth = getViewWidth()
                if (mMoveOldX >= viewWidth) {
                    mMoveOldX = viewWidth
                }
            }
        }
        return true
    }

二、首要代码进行刨析

1、制作布景

布景没什么好说的,便是一个简略的圆角矩形,运用drawRoundRect制作即可,需求确认的是左上右下的间距。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:制作布景
     */
    private fun canvasBackground(canvas: Canvas) {
        mPaint!!.color = mProgressBackground
        val rect = RectF().apply {
            left = mProgressMarginLeftRight
            top = mProgressMarginTopBottom
            right = width.toFloat() - mProgressMarginLeftRight
            bottom = mProgressHeight + mProgressMarginTopBottom
        }
        canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mPaint!!)
    }

2、制作离散间隔

离散间隔,需求确认,间隔数,然后依据间隔数量,动态的核算每个间隔的方位,能够运用drawLine制作一个小小的竖线,竖线也需求确认间隔上下的间隔和自身的宽度;特殊情况下,离散间隔,在滑动前后的色彩是不相同的,所以这儿也做了一个动态改动色彩的判别。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:制作离散间隔
     */
    private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
        val rect =
            (width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
        if (isCanvas) {
            mPaint!!.color = mIntervalSelectColor
        } else {
            mPaint!!.color = mIntervalColor
        }
        mPaint!!.strokeWidth = mIntervalWidth
        for (a in 0..mIntervalSize) {
            val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
            val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
            canvas.drawLine(
                x,
                y,
                x,
                mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
                mPaint!!
            )
        }
    }

3、制作移动thumb

关于thumb,首先要确认的便是巨细,假如设置了宽高,那么就需求运用Bitmap从头设置高度,改动thumb的坐标,只需求不断的改动图片的left坐标点即可,也便是通过上述的Move事情的中移动坐标来设置。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:制作移动的图标
     */
    private fun canvasMoveIcon(canvas: Canvas) {
        mProgressThumb?.let {
            var decodeResource = BitmapFactory.decodeResource(resources, it)
            mProgressThumbWidth = decodeResource.width
            if (mThumbWidth != 0f) {
                val height: Int = decodeResource.height
                // 设置想要的巨细
                val newWidth = mThumbWidth
                val newHeight = mThumbHeight
                // 核算缩放份额
                val scaleWidth = newWidth / width
                val scaleHeight = newHeight / height
                // 取得想要缩放的matrix参数
                val matrix = Matrix()
                matrix.postScale(scaleWidth, scaleHeight)
                // 得到新的图片
                decodeResource =
                    Bitmap.createBitmap(decodeResource, 0, 0, width, height, matrix, true)
            }
            var mThumpLeft = mMoveProgress
            if (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:制作进展
     */
    private fun canvasMoveProgress(canvas: Canvas) {
        //为空
        if (mColorArray.isEmpty()) {
            mColorArray = intArrayOf(
                ContextCompat.getColor(context, R.color.text_ff3e3e93),
                ContextCompat.getColor(context, R.color.text_ff8548d2),
            )
        }
        val linearShader = LinearGradient(
            0f,
            0f,
            mMoveProgress + mProgressMarginLeftRight,
            mProgressHeight,
            mColorArray,
            floatArrayOf(0f, 1f),
            Shader.TileMode.CLAMP
        )
        mProgressPaint!!.shader = linearShader
        //等于0时
        val rect = RectF()
        rect.left = mProgressMarginLeftRight
        rect.top = mProgressMarginTopBottom
        rect.right = mMoveProgress + mProgressMarginLeftRight
        rect.bottom = mProgressHeight + mProgressMarginTopBottom
        canvas.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:制作离散间隔
     */
    private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
        val rect =
            (width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
        if (isCanvas) {
            mPaint!!.color = mIntervalSelectColor
        } else {
            mPaint!!.color = mIntervalColor
        }
        mPaint!!.strokeWidth = mIntervalWidth
        for (a in 0..mIntervalSize) {
            val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
            val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
            canvas.drawLine(
                x,
                y,
                x,
                mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
                mPaint!!
            )
            //制作刻度值
            if (mIsGraduation && isCanvas) {
                if (mHideGraduationSectionCenter && (a != 0 && a != mIntervalSize)) {
                    //躲藏中心
                    continue
                }
                var graduation = a * mGraduationSection
                //是否从0开端记录
                if (graduation == 0 && !mGraduationSectionZero) {
                    graduation = 1
                }
                //假如移动到了,改动色彩
                if (mGraduationResult >= graduation && mGraduationResult < graduation + mGraduationSection) {
                    mGraduationPaint?.color = mGraduationSelectTextColor
                } else {
                    mGraduationPaint?.color = mGraduationTextColor
                }
                val text = graduation.toString()
                val rectText = Rect()
                mGraduationPaint!!.getTextBounds(text, 0, text.length, rectText)
                val textWidth = rectText.width()
                val textHeight = rectText.height()
                canvas.drawText(
                    text,
                    x - textWidth / 2,
                    mProgressHeight + mProgressMarginTopBottom * 2 + textHeight + mGraduationMarginTop,
                    mGraduationPaint!!
                )
            }
        }
    }

三、开源地址及运用方法

目前现已上传到了Github,本身就一个简略的类,没多少东西,需求的铁子,能够直接检查源码即可。

地址:github.com/AbnerMing88…

假如懒得下载源码,想直接运用,没得问题,我现已上传到了远程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.MoveProgress
    android: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制作线,圆角矩形,图片以及和手势结合的相关知识点,本身并没有难点。