一、需求确认

首先我们要明确需求,要做一个什么样的Seekbar,分析清楚业务需求,再开始做。如图,产品大佬给的图是这样子的:

当然,作为一个工程师,第一步当然是去问问度娘,看有没有好的轮子,然后去github上淘淘金。我始终认为这是一个优秀工程师该有的解决问题的方法,哈哈~ 找过一圈之后,发现并没有适合的轮子可以用,这时心里开始咒骂产品了,“提的什么鬼需求,那么非主流”。但是骂完还得撸起袖子干啊。

二、该怎么做

首先我们分析,这个控件该怎么去实现。首先,我们知道,要画一条直线来当作进度条;然后呢,我们要画一个圆角矩形,矩形中要有数字。查看了SeekBar的源码,发现他有一个监听器回调滑杆事件,那么咱们也要有。好的,现在确认了需要的4样东西:直线、圆角矩形、数字、监听器。就可开工了。

三、开整

我们首先顶一个半径pointRadius,因为滑杆一般为圆形或者圆角正方形。还有线的高度lineHeight,progress进度值。这里还是贴代码了。

    private int pointRadius = 45;//圆脚默认半径private int pointColor = R.color.dream_2;//圆脚默认颜色private int lineHeight = 10;//线默认高度private int lineClor = R.color.dream_1;//线默认颜色private int progress = 0;private final int PROGRESS_MIN = 0;private final int PROGRESS_MAX = 100;

当然,要画三个东西,就要准备三支笔。

    private Paint linePaint;private Paint pointPaint;private Paint textPaint;

最后还有一个监听器,三个回调方法分别是开始的时候回调,滑动中回调,滑动完回调。当然可以根据自己的逻辑增加回调方法。

  private OnProgressChangedListener progressChangedListener;public interface OnProgressChangedListener {void onStartChange(View view);void onProgressChange(View view, int progress);void onProgressChanged(View view, int progress);}

首先,我们画一条线

canvas.drawLine(pointRadius / 2, getHeight() / 2, getWidth() - pointRadius / 2, getHeight() / 2, linePaint);

然后画个圆角矩形(正方形)

        RectF r2 = new RectF();                           //RectF对象r2.left = getCx() - pointRadius;                                 //左边r2.top = getHeight() / 2 - pointRadius;                                 //上边r2.right = getCx() + pointRadius;                                   //右边r2.bottom = getHeight() / 2 + pointRadius;                              //下边canvas.drawRoundRect(r2, 20, 20, pointPaint);

然后就是画字了(文本)

        if (progress > 9 && progress < 100) {canvas.drawText("" + progress, getCx() - 32, getHeight() / 2 + pointRadius / 3, textPaint);} else if (progress > 99) {canvas.drawText("" + progress, getCx() - pointRadius + 2, getHeight() / 2 + pointRadius / 3, textPaint);} else {canvas.drawText("" + progress, getCx() - pointRadius / 3, getHeight() / 2 + pointRadius / 3, textPaint);}

这里说下为啥要分三种情况,因为0-100分为一位数,二位数,三位数。每种情况文本的宽度都不同,所以对应的在x轴上的偏移量也不相同,所以这里分了三种情况去绘制。

读到这里,也许有人要问了,getCx()这家伙是干嘛的。这个是滑杆的X坐标。那么这个坐标怎么获取,因为滑杆滑动,这个值肯定是一个动态的。

    private float getCx() {float cx = 0.0f;cx = (getWidth() - pointRadius * 2);if (cx < 0) {throw new IllegalArgumentException("TouchProgressView 宽度不可以小于 2 倍 pointRadius");}return cx / 100 * progress + pointRadius;}

这个公式是怎么来的呢,看下图:

pro是当前的进度progress,r为滑杆的一半,我们需要的坐标值,就是图中的那个红点。怎么样,一目了然吧。

图画完了,接下来就是让我们的进度条动起来了。怎么动呢,我们通过改变progress的值,然后调用invalidate()方法,就可以实现进度条的滑动了。

   public void setProgress(int progress) {if (progress < 0 || progress > 100) {throw new IllegalArgumentException("progress 不可以小于0 或大于100");}this.progress = progress;invalidate();if (progressChangedListener != null) {progressChangedListener.onProgressChange(this, progress);}}

同时,我们还有回调进度监听。什么时候让它动呢,当然使我们手触摸的时候,想都不用想,要重写onTouchEvent方法。

 /*** 回调进度完成接口*/private void callBackListener(int progress) {if (progressChangedListener != null) {progressChangedListener.onProgressChanged(this, progress);}}private void callBackIngListener() {if (progressChangedListener != null) {progressChangedListener.onStartChange(this);}}

我们这里先对进度回调做了个简单的封装。下面我们开始处理onTouchEvent。

@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i("cqc", "event.getAction=" + event.getAction());lastX = event.getX();if (event.getX() < pointRadius) {setProgress(PROGRESS_MIN);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:callBackIngListener();return true;case MotionEvent.ACTION_MOVE:callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(PROGRESS_MIN);return true;default:return true;}} else if (event.getX() > getWidth() - pointRadius) {setProgress(PROGRESS_MAX);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:callBackIngListener();return true;case MotionEvent.ACTION_MOVE:callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(PROGRESS_MAX);return true;default:return true;}} else {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:setProgress(calculProgress(event.getX()));callBackIngListener();return true;case MotionEvent.ACTION_MOVE:setProgress(calculProgress(event.getX()));callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(calculProgress(event.getX()));return true;}}return super.onTouchEvent(event);}

至此,我们的自定义View重要的点讲完了。可能有些大神会觉得很简单,但是笔者写这篇博客的初衷是帮助初级工程师们对自定义控件的掌握,所以写的有点多了。下面是效果图和源码。


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.ColorRes;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;/*** Created by chenqc on 2018/11/6.*/public class TouchProgressView extends View {private static final String TAG = "TouchProgressView";private Paint linePaint;private Paint pointPaint;private Paint textPaint;private float lastX = 0;private int pointRadius = 45;//圆角默认半径private int pointColor = R.color.dream_2;//圆角默认颜色private int lineHeight = 10;//线默认高度private int lineClor = R.color.dream_1;//线默认颜色private int progress = 0;private final int PROGRESS_MIN = 0;private final int PROGRESS_MAX = 100;private OnProgressChangedListener progressChangedListener;public interface OnProgressChangedListener {void onStartChange(View view);void onProgressChange(View view, int progress);void onProgressChanged(View view, int progress);}public TouchProgressView(Context context) {super(context, null);}public TouchProgressView(Context context, AttributeSet attrs) {super(context, attrs);}public TouchProgressView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 设置圆角半径** @param radius*/public void setPointRadius(final int radius) {if (radius <= 0) {throw new IllegalArgumentException("radius 不可以小于等于0");}if (getWidth() == 0) {post(new Runnable() {@Overridepublic void run() {if (radius * 2 > getWidth()) {throw new IllegalArgumentException("radius*2 必须小于 view.getWidth() == " + getWidth());}pointRadius = radius;}});} else {if (radius * 2 > getWidth()) {throw new IllegalArgumentException("radius*2 必须小于 view.getWidth() == " + getWidth());}this.pointRadius = radius;}}/*** 设置圆角颜色** @param color*/public void setPointColor(@ColorRes int color) {this.pointColor = color;}/*** 设置直线高度** @param height*/public void setLineHeight(int height) {if (height <= 0) {throw new IllegalArgumentException("height 不可以小于等于0");}this.lineHeight = height;}/*** 设置直线颜色** @param color*/public void setLineColor(@ColorRes int color) {this.lineClor = color;}/*** 设置百分比** @param progress*/public void setProgress(int progress) {if (progress < 0 || progress > 100) {throw new IllegalArgumentException("progress 不可以小于0 或大于100");}this.progress = progress;invalidate();if (progressChangedListener != null) {progressChangedListener.onProgressChange(this, progress);}}/*** 滑动propress,不回调监听*/public void slideProgress(int progress) {if (progress < 0 || progress > 100) {throw new IllegalArgumentException("progress 不可以小于0 或大于100");}this.progress = progress;invalidate();}public int getProgress() {return progress;}/*** 回调进度完成接口*/private void callBackListener(int progress) {if (progressChangedListener != null) {progressChangedListener.onProgressChanged(this, progress);}}private void callBackIngListener() {if (progressChangedListener != null) {progressChangedListener.onStartChange(this);}}/*** 设置进度变化监听器** @param onProgressChangedListener*/public void setOnProgressChangedListener(OnProgressChangedListener onProgressChangedListener) {this.progressChangedListener = onProgressChangedListener;}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i("cqc", "event.getAction=" + event.getAction());lastX = event.getX();if (event.getX() < pointRadius) {setProgress(PROGRESS_MIN);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:callBackIngListener();return true;case MotionEvent.ACTION_MOVE:callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(PROGRESS_MIN);return true;default:return true;}} else if (event.getX() > getWidth() - pointRadius) {setProgress(PROGRESS_MAX);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:callBackIngListener();return true;case MotionEvent.ACTION_MOVE:callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(PROGRESS_MAX);return true;default:return true;}} else {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:setProgress(calculProgress(event.getX()));callBackIngListener();return true;case MotionEvent.ACTION_MOVE:setProgress(calculProgress(event.getX()));callBackIngListener();return true;case MotionEvent.ACTION_UP:callBackListener(calculProgress(event.getX()));return true;}}return super.onTouchEvent(event);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overridepublic void draw(Canvas canvas) {super.draw(canvas);linePaint = new Paint();linePaint.setAntiAlias(true);linePaint.setStyle(Paint.Style.FILL);linePaint.setStrokeCap(Paint.Cap.ROUND);linePaint.setStrokeWidth(lineHeight);linePaint.setColor(getResources().getColor(lineClor));canvas.drawLine(pointRadius / 2, getHeight() / 2, getWidth() - pointRadius / 2, getHeight() / 2, linePaint);pointPaint = new Paint();pointPaint.setAntiAlias(true);pointPaint.setStyle(Paint.Style.FILL);pointPaint.setColor(getResources().getColor(pointColor));RectF r2 = new RectF();                           //RectF对象r2.left = getCx() - pointRadius;                                 //左边r2.top = getHeight() / 2 - pointRadius;                                 //上边r2.right = getCx() + pointRadius;                                   //右边r2.bottom = getHeight() / 2 + pointRadius;                              //下边canvas.drawRoundRect(r2, 20, 20, pointPaint);//canvas.drawCircle(getCx(), getHeight() / 2, pointRadius, pointPaint);textPaint = new Paint();textPaint.setAntiAlias(true);textPaint.setStyle(Paint.Style.FILL);textPaint.setTypeface(Typeface.DEFAULT_BOLD);textPaint.setTextSize(50);textPaint.setColor(getResources().getColor(R.color.dream_text));Log.i("cqc", "progress=" + progress);if (progress > 9 && progress < 100) {canvas.drawText("" + progress, getCx() - 32, getHeight() / 2 + pointRadius / 3, textPaint);} else if (progress > 99) {Log.i("cqc", "11111");canvas.drawText("" + progress, getCx() - pointRadius + 2, getHeight() / 2 + pointRadius / 3, textPaint);} else {canvas.drawText("" + progress, getCx() - pointRadius / 3, getHeight() / 2 + pointRadius / 3, textPaint);}}/*** 获取圆角的x轴坐标** @return*/private float getCx() {float cx = 0.0f;cx = (getWidth() - pointRadius * 2);if (cx < 0) {throw new IllegalArgumentException("TouchProgressView 宽度不可以小于 2 倍 pointRadius");}return cx / 100 * progress + pointRadius;}/*** 计算触摸点的百分比** @param eventX* @return*/private int calculProgress(float eventX) {float proResult = (eventX - pointRadius) / (getWidth() - pointRadius * 2);return (int) (proResult * 100);}}

Android自定义View之滑杆内部带数字的SeekBar相关推荐

  1. Android自定义View之Layout内部布局自定义

    目录 每日一句 作者简介 概述 1.定制Layout内部布局的方式步骤 1.1重写onMeasure()方法来计算内部布局 1.1.1 调用每个子View的measure(),让子View自我测量 1 ...

  2. [Android]自定义View带效果的滚动数字

    [Android]自定义View带效果的滚动数字 @Author GQ 2016年07月29日 一个可以让数字滚动的View,可以自定义参数,是想要的那种效果! 原文github地址 效果图 Andr ...

  3. Android 自定义view完全解析--带你通透了解自定义view

    参考转自郭霖博客带你一步步深入了解View系列 Android LayoutInflater原理分析 相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用 ...

  4. android 自定义View绘制电池电量(电池内带数字显示)

    最新公司需要一个电池内带数字的显示电池电量需求,百度了一下.参考下面这篇文章写的Android自定义View之电池电量显示. 增加了里面电池电量数字显示,还有就是一个屏幕适配.不管屏幕分辨率基本都能适 ...

  5. 一篇文章带你走近Android自定义view

    系列文章目录 一篇文章带你走近Android自定义view 文章目录 系列文章目录 前言 一.为什么要自定义view 二.先看看一个超级简单的自定义view(三个构造函数) 三.了解手机的坐标系 四. ...

  6. android 两边圆角,Android自定义View实现带4圆角或者2圆角的效果

    1 问题 实现任意view经过自定义带4圆角或者2圆角的效果 2 原理 1) 实现view 4圆角 我们只需要把左边的图嵌入到右边里面去,最终显示左边的图就行. 2) 实现view上2圆角 我们只需要 ...

  7. Android 自定义View之随机数验证码(仿写鸿洋)

    前言 本文面向自定义view新手,但是希望你最好有一定的理论知识,或基础概念,有的地方可能会一笔带过并不会细讲,细讲篇幅就太长了. 本文仿写自鸿洋的自定义View (一),尽管过去了将近快7年之久,我 ...

  8. android中仿qq最新版抽屉,Android 自定义View实现抽屉效果

    Android 自定义View实现抽屉效果 说明 这个自定义View,没有处理好多点触摸问题 View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子 ...

  9. Android 自定义View

    [Android 自定义View] Android 自定义View 自定义View基础 自定义TextView 继承View重写onDraw方法 View的构造方法 自定义属性 创建attrsxml文 ...

最新文章

  1. 腾讯云宣布核心产品全线降价,最高降幅达50%
  2. 《转》python学习--基础上
  3. 我犯的错误--关于数据库类型不对
  4. java反射学习(1):反射的基本操作
  5. 计算机的指令合成为,第二章计算机操作基础知识doc
  6. mysql的索引缺点_「缺点有哪些」数据库索引是什么 有什么优缺点 - seo实验室
  7. 谷歌中文为什么以红色高亮关键字
  8. 【系统架构师修炼之道】(8):绪论——DoDAF框架
  9. 树莓派初始用户名和密码
  10. 百度邓凯鹏:飞桨视觉技术解析与应用
  11. Deepfake——深度造假视频在智能城市中的风险
  12. MTTF、MTTR、MTBF
  13. Electron 实现百度快搜
  14. 转行智能控制的这些年
  15. Java历史、现状和各版本技术更迭总结
  16. php——webshell变形总结
  17. 尚硅谷智慧校园-3、年级管理系统的实现
  18. javaScript回调函数
  19. 计算机毕设Node.js+Vue兴澜幼儿园管理系统(程序+LW+部署)
  20. oracle空间查询定义,ORACLE SDO_GEOMETRY 空间数据创建查询

热门文章

  1. [blockchain-042]eos 硅谷ecs docer编译 mongo存储
  2. 使用GANs生成时间序列数据:DoppelGANger论文详解
  3. win7电脑给手机开热点流程
  4. win7系统盘瘦身秘诀
  5. 如何升级composer
  6. 【错误解决】Spring JPA的错误及其解决方案
  7. 研究生复试英语问答口语10个最可能问到的问题
  8. 贝叶斯分类器,什么是朴素贝叶斯,后续为使用贝叶斯实现海量数据的邮件筛选。带源码数据集和解决思路
  9. 谷歌3d卫星地图下载
  10. iPhone手机使用:手机上面的App Store突然变成英文(iTunes也是英文),然后把英文还原成中文的方法