欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android实现可拖拽带有坐标尺进度条的示例代码,

来源: 开发者 投稿于  被查看 44808 次 评论:55

Android实现可拖拽带有坐标尺进度条的示例代码,


目录
  • 一、分析要素,确定实现方案
    • 1、设置默认高度
    • 2、拖动事件
  • 二、主要代码进行刨析
    • 1、绘制背景
    • 2、绘制离散间隔
    • 3、绘制移动thumb
    • 4、绘制移动进度
    • 5、绘制文字刻度
  • 三、开源地址及使用方式
    • 相关属性
    • 相关方法
  • 四、总结

    拿到下面的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/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.MoveProgress
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:ms_graduation_hide_center="true" />

    相关属性

    属性类型概述
    ms_heightdimensionView视图的高度
    ms_progress_heightdimension进度条的高度
    ms_progress_thumbreference进度条的Icon
    ms_progress_margin_top_bottomdimension进度条距离icon的上下距离
    ms_progress_margin_left_rightdimension进度条距离左右的边距
    ms_progress_radiusdimension进度条的圆角
    ms_progress_backgroundcolor进度条的背景颜色
    ms_interval_colorcolor间隔线颜色
    ms_interval_select_colorcolor间隔线选中颜色
    ms_interval_parent_margin_left_rightdimension间隔线距离父左右
    ms_interval_sizeinteger间隔线数量
    ms_interval_widthdimension间隔线宽度
    ms_interval_margin_top_bottomdimension间隔线上下边距
    ms_progress_move_colorreference定义的移动颜色
    ms_progress_maxinteger最大进度
    ms_progress_defaultinteger默认进度
    ms_is_graduationboolean是否显示刻度尺
    ms_graduation_text_sizedimension刻度尺文字大小
    ms_graduation_text_colorcolor刻度尺文字颜色
    ms_graduation_select_text_colorcolor刻度尺文字选中颜色
    ms_graduation_sectioninteger刻度值段
    ms_graduation_section_zeroboolean刻度值段从零开始
    ms_graduation_hide_centerboolean刻度值段中间是否隐藏
    ms_graduation_margin_topdimension刻度值距离上边的距离
    ms_progress_thumb_widthdimensionicon的宽
    ms_progress_thumb_heightdimensionicon的高
    ms_progress_thumb_margin_topdimensionicon距离上边的高度
    ms_progress_thumb_spacingdimensionicon的内边距
    ms_progress_disallow_interceptboolean是否拦截
    ms_progress_is_interceptboolean是否禁止拖拽

    相关方法

    方法参数概述
    getProgress无参返回当前进度
    changeProgressInt改变当前进度
    getMoveProgress返回Int回调函数
    setProgressIsInterceptBoolean设置是否进行拦截

    四、总结

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

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

    以上就是Android实现可拖拽带有坐标尺进度条的示例代码的详细内容,更多关于Android可拖拽进度条的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • Android自定义垂直拖动seekbar进度条
    • Android seekbar实现可拖动进度条
    • android自定义进度条移动效果
    • Android实现绚丽的自定义进度条
    • Android自定义view实现圆环进度条效果
    • Android ProgressBar实现进度条效果
    • Android自定义View实现圆形进度条

    用户评论