自定义View之HProgressView(水平进度条view)

写在前面的话

话说kotlin被google确立正统地位(Android开发官方语言)已经快一年了,之前也断断续续学习过kotlin,但是由于实际项目开发中并未使用kotlin,因此kotlin并不熟悉。由于最近偶得闲暇,于是重拾kotlin,并计划使用kotlin重构之前写过的app,以此作为自己一段成长记录。
本文是kotlin实现的水平方向带进度条(支持动画)的自定义view。

1.效果图

不知不觉又扯远了,好了,回归正题,直接来看效果图:


h_progress_view.gif

2.实现思路

  • 绘制默认进度条(默认进度条两端是半圆,中间是矩形)
  • 绘制进度条经过的区域(左边是半圆,右边矩形,当进度条为100%时,最右的半圆也要绘制)
  • 绘制进度条下方的说明文字以及实际进度数值

3.细节阐述

3.1概述

该View舍弃继承ProgressBar,而继承自View,实现高度的可定制,尤其是该进度条两端是一些花哨的几何图形时,该View两端暂定为半圆(当然,如果两端图形不计较是啥时,继承ProgressBar会更轻松),首先重写View的三个构造方法,并初始化必要参数,接着获取该View宽高以及必要计算参数,然后就是绘制该View了,最后重写触摸事件方法,让该View响应点击以及滑动事件。

3.2

通过res/values/attrs定义如下细节参数

   <declare-styleable name="HProgressView">
        <attr name="progress" format="float" />
        <!--水平方向边距-->
        <attr name="h_margin" format="dimension" />
        <attr name="progress_color" format="color" />
        <attr name="normal_color" format="color" />
        <attr name="text_des_color" format="color" />
        <attr name="text_num_color" format="color" />
        <attr name="text_size" format="dimension" />
        <!--进度条的纵向宽度-->
        <attr name="progress_v_width" format="dimension" />
        <attr name="text_to_progress_margin" format="dimension" />
        <attr name="with_anim" format="boolean" />
        <attr name="with_click" format="boolean" />
    </declare-styleable>

3.3在构造方法中初始化attrs参数以及画笔paint

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        defProgressColor = ContextCompat.getColor(context, R.color.progress_color)
        defNormalColor = ContextCompat.getColor(context, R.color.progress_normal_color)
        defTxtDesColor = ContextCompat.getColor(context, R.color.progress_txt_des_color)
        defTxtNumColor = ContextCompat.getColor(context, R.color.progress_txt_num_color)
        defTxtSize = dip2px(16f)
        defProgressVWidth = dip2px(20f)
        defTxt2ProgressMargin = dip2px(30f)
        defHMargin = dip2px(10f)

        val a = context.obtainStyledAttributes(attrs, R.styleable.HProgressView)
        try {
            mProgress = a.getFloat(R.styleable.HProgressView_progress, 0f)
            hMargin = a.getDimension(R.styleable.HProgressView_h_margin, defHMargin)
            progressColor = a.getColor(R.styleable.HProgressView_progress_color, defProgressColor)
            normalColor = a.getColor(R.styleable.HProgressView_normal_color, defNormalColor)
            txtDesColor = a.getColor(R.styleable.HProgressView_text_des_color, defTxtDesColor)
            txtNumColor = a.getColor(R.styleable.HProgressView_text_num_color, defTxtNumColor)
            txtSize = a.getDimension(R.styleable.HProgressView_text_size, defTxtSize)
            progressVWidth = a.getDimension(R.styleable.HProgressView_progress_v_width, defProgressVWidth)
            txt2ProgressMargin = a.getDimension(R.styleable.HProgressView_text_to_progress_margin, defTxt2ProgressMargin)
            withAnim = a.getBoolean(R.styleable.HProgressView_with_anim, true)
        } finally {
            a.recycle()
        }

        if (mProgress < 0) mProgress = 0f
        if (mProgress > 100) mProgress = 100f

        normalPaint.color = normalColor
        normalPaint.style = Paint.Style.FILL

        progressPaint.color = progressColor
        progressPaint.style = Paint.Style.FILL

        txtPaint.style = Paint.Style.FILL
        txtPaint.textSize = txtSize

        bounds = Rect()
    }

3.4在onSizeChanged方法中,获取该View宽高以及必要计算参数

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w
        mHeight = h
        cirlceRadius = progressVWidth / 2
        startCenterX = cirlceRadius + hMargin
        endCenterX = mWidth - cirlceRadius - hMargin
        startCenterY = mHeight / 2 - txt2ProgressMargin / 2
        endCenterY = startCenterY

        top = startCenterY - cirlceRadius
        bottom = startCenterY + cirlceRadius
    }

3.5绘制View

3.5.1普通进度条
    private fun drawNormal(canvas: Canvas) {
        recLeftCircle = RectF(hMargin, top, hMargin + progressVWidth, bottom)
        canvas.drawArc(recLeftCircle, 90f, 180f, true, normalPaint)

        recRightCircle = RectF(mWidth - progressVWidth - hMargin, top, mWidth - hMargin, bottom)
        canvas.drawArc(recRightCircle, -90f, 180f, true, normalPaint)

        rectProgressArea = RectF(hMargin + cirlceRadius, top, mWidth - hMargin - cirlceRadius, bottom)
        canvas.drawRect(rectProgressArea, normalPaint)
    }
3.5.2完成进度条
        private fun drawProgress(canvas: Canvas) {
        if (mProgress > 0) {
            canvas.drawArc(recLeftCircle, 90f, 180f, true, progressPaint)

            if (mProgress >= 0.5f) {//为了防止mprogress为0.1-0.5时左边绘制有误
                val left = hMargin + cirlceRadius
                val right = (mWidth - 2 * hMargin - progressVWidth) * mProgress / 100 + hMargin + cirlceRadius
                rectProgressPass = RectF(left, top, right, bottom)
                canvas.drawRect(rectProgressPass, progressPaint)

                rectProgressPass = RectF(right - cirlceRadius, top, right + cirlceRadius, bottom)
                canvas.drawArc(rectProgressPass, -90f, 180f, true, progressPaint)
            }
        }
    }

3.5.3说明文字以及进度数值
    private fun drawDesTxt(canvas: Canvas) {
        val des = "已完成${mProgress}%"
        txtPaint.getTextBounds(des, 0, des.length, bounds)
        //获取整个说明文字宽高
        val desW = bounds.width()
        val desH = bounds.height()
        //获取“已完成”三个字宽度
        val lw = txtPaint.measureText(des, 0, 3)

        txtPaint.color = txtDesColor
        canvas.drawText(des, 0, 3, mWidth / 2 - desW / 2f, mHeight / 2 + 
               defTxt2ProgressMargin / 2 + desH / 2, txtPaint)

        txtPaint.color = txtNumColor
        canvas.drawText(des, 3, des.length, mWidth / 2 - desW / 2f + lw, 
               mHeight / 2 + defTxt2ProgressMargin / 2 + desH / 2, txtPaint)
    }

3.6处理View添加Touch事件

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_MOVE -> dealMotionEvent(event.x, event.y, true)//默认滑动事件始终伴随动画
            MotionEvent.ACTION_UP -> dealMotionEvent(event.x, event.y, withAnim)
        }
        return true
    }

    private fun dealMotionEvent(x: Float, y: Float, withAnim: Boolean) {
        val rectLeftHalf = RectF(hMargin, top, hMargin + cirlceRadius, bottom)
        val rectRightHalf = RectF(mWidth - hMargin - cirlceRadius, top, mWidth - hMargin, bottom)
        if (rectLeftHalf.contains(x, y)) setProgress(0f)
        if (rectRightHalf.contains(x, y)) setProgress(100f)
        if (rectProgressArea.contains(x, y)) {
            val progress = (x - hMargin - cirlceRadius) / (mWidth - 2 * hMargin - progressVWidth) * 100f
            setProgress(String.format("%.1f", progress).toFloat(), withAnim)
        }
    }

3.7设置对外调用接口

    fun setWithAnim(withAnim: Boolean) {//设置是否支持动画
        this.withAnim = withAnim
    }

    fun setProgress(progress: Float) = setProgress(progress, withAnim)//方法重载

    fun setProgress(progress: Float, withAnim: Boolean) {//方法重载
        if (progress < 0) mProgress = 0f
        if (progress > 100) mProgress = 100f
        if (withAnim) {
            val animator = ValueAnimator.ofFloat(mProgress, progress)
            animator.addUpdateListener { anim ->
                mProgress = String.format("%.1f", anim.animatedValue).toFloat()
                invalidate()
            }
            animator.interpolator = LinearInterpolator()
            animator.duration = (Math.abs(mProgress - progress) * 50).toLong()
            animator.start()
        } else {
            mProgress = progress
            invalidate()
        }
    }

4.调用

4.1xml调用

    <com.ddlc.hprogress.HProgressView
        android:id="@+id/hpv_progress"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        tangbuzhi:h_margin="20dp"
        tangbuzhi:normal_color="#66000000"
        tangbuzhi:progress="34.5"
        tangbuzhi:progress_color="#ff00ff"
        tangbuzhi:progress_v_width="15dp"
        tangbuzhi:text_des_color="#000fff"
        tangbuzhi:text_num_color="#ff00ff"
        tangbuzhi:text_size="16dp"
        tangbuzhi:text_to_progress_margin="30dp"
        tangbuzhi:with_anim="false"
        tangbuzhi:with_click="false" />

4.2java调用

hpv_progress.setWithAnim(true)
hpv_progress.setWithClick(true)
hpv_progress.setProgress(progress, true)

5.代码托管地址

水平方向带进度条(支持动画)的HProgressView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容