自定义 View 实战(一)做一个简单的进度条
2018-02-06 10:29
316 查看
前言
自定义 View 是每个 Android 程序员走向高级必经之路,本篇通过实现一个非常简单的自定义 View ,来简单了解下自定义 View 的流程。(最后会给出源码)先看下效果:
![](http://p2oza5z62.bkt.clouddn.com/2018-02-05-15178462831619.gif)
录制的 gif 可能看不清,欢迎去 Github下载项目运行查看。
一、分析需求
这个 View 是我前段时间做公司项目的时候写的,要求的功能比较简单:根据给出的百分比显示进度条
中间一直存在的线条
进度条的颜色
线条的颜色
进度条是否有动画效果
需求简单,所以实现起来也很简单的,接下来就一步一步的实现。
二、定义属性并获取
根据上面的分析,我们在 res/values 下面新建文件 attrs.xml,定义我们需要的属性如下:<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LineProgressBar"> <!--进度条颜色--> <attr name="progress_color" format="color" /> <!--中间线的颜色--> <attr name="progress_line_color" format="color" /> <!--进度条的值--> <attr name="progress" format="integer" /> <!--是否有动画效果--> <attr name="is_smooth_progress" format="boolean" /> </declare-styleable> </resources>
上面的属性的定义是在布局文件中使用的。
然后我们需要在自定义的 View 里面对应获取 xml 中定义的属性。
由于我们定义的是进度条,需要有最大值,根据百分比来显示进度。
新建 LineProgressBar 继承于 View:
/** * @author smartsean */ public class LineProgressBar extends View { //进度条的最大值 private static final int MAX_PROGRESS = 100; //默认中间线颜色 private static final int DEFAULT_LINE_COLOR = Color.parseColor("#e6e6e6"); //默认进度条颜色 private static final int DEFAULT_PROGRESS_COLOR = Color.parseColor("#71db77"); /** * progress底部线的画笔 */ private Paint linePaint; /** * progress画笔 */ private Paint progressPaint; /** * progress底部线的颜色 */ private int lineColor; /** * progress的颜色 */ private int progressColor; /** * 进度值 百分比 */ private float progress; /** * 是否平滑显示progress */ private boolean isSmoothProgress; public LineProgressBar(Context context) { this(context, null); } public LineProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } }
接下来,我们需要在
init(context, attrs)里面获取
attrs.xml中定义的属性,并进行一些初始化。
/** * 初始化参数 */ private void init(Context context, AttributeSet attrs) { TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.LineProgressBar); lineColor = attributes.getColor(R.styleable.LineProgressBar_progress_line_color, DEFAULT_LINE_COLOR); progressColor = attributes.getColor(R.styleable.LineProgressBar_progress_color, DEFAULT_PROGRESS_COLOR); progress = attributes.getInteger(R.styleable.LineProgressBar_progress, 0) / MAX_PROGRESS; isSmoothProgress = attributes.getBoolean(R.styleable.LineProgressBar_is_smooth_progress, true); attributes.recycle(); initializePainters(); }
三、测量
重写onMeasure方法,测量 View 的真实宽高
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false)); } private int measure(int measureSpec, boolean isWidth) { int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom(); if (mode == MeasureSpec.EXACTLY) { result = size; } else { result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); result += padding; if (mode == MeasureSpec.AT_MOST) { if (isWidth) { result = Math.max(result, size); } else { result = Math.min(result, size); } } } return result; }
分析之前先看几个概念: 一个
MeasureSpec被分为两部分
- mode 用来存储测量模式,由 MeasureSpec 的高两位存储
- size 用来存储大小,由 MeasureSpec 的低30位存储
mode 模式分为三种:
- UNSPECIFIED 未指定模式,View 想多大就多大,父容器不做限制,一般用于系统内部的测量
- AT_MOST :最大模式,对应于 layout_width 或者 layout_height 设置为 wrap_content ,子 View 的最终大小是最终父 View 指定的 size 值,并且子 View 的最终大小不能大于这个 size 值。
- EXACTLY 精确模式,对应于layout_width 或者 layout_height 设置为 match_parent 或者 具体的值(比如12dp),父容器测量出 View 所需的大小,也就是 size 的值。
接下来开始具体的测量,首先 measure 是一个公共方法,用来测量 View 宽和高。
这里以测量宽为例分析下 measure 方法:
int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec);
MeasureSpec.getMode(measureSpec) 获得测量模式 mode
MeasureSpec.getSize(measureSpec) 大小 size。
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
如果是测量宽,获取左侧和右侧的 padding 之和赋值给 padding。
如果是测量高,获取底部和顶部的 padding 之和赋值给 padding。
if (mode == MeasureSpec.EXACTLY) { result = size; } else { result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); result += padding; if (mode == MeasureSpec.AT_MOST) { if (isWidth) { result = Math.max(result, size); } else { result = Math.min(result, size); } } }
如果测量值为精确模式 MeasureSpec.EXACTLY ,View 已经明确了自己的大小,那么直接返回 size。
如果测量模式是 UNSPECIFIED 或者 AT_MOST,取 getSuggestedMinimumWidth ,那么这个 getSuggestedMinimumWidth() 是什么呢?
来看下源码:
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
可以看到,先判断了背景 mBackground 是不是为空
如果为空,就直接取 mMinWidth 的值,也就是对应 xml 中的
android:minWidth
如果不为空,取 mMinWidth 和 mBackground 背景的最小宽度。
具体到本实例就是说:如果你设置了 wrap_content,
设置了 minWidth ,没有设置背景,就按照 minWidth
设置了 minWidth ,有设置背景,就取 minWidth 和 mBackground.getMinimumWidth() 的最大值
没设置 minWidth ,没有设置背景,就取 minWidth 的默认值0.
没设置了 minWidth ,有设置背景,就取 minWidth(默认值为0) 和 mBackground.getMinimumWidth() 的最大值
mBackground.getMinimumWidth() 就是背景 Drawable 的原始宽高。
有可能我们的 View 设置了 leftPadding 或者 rightPadding,然后再把上面计算的 padding 加到 result 上。
如果测量模式是 UNSPECIFIED ,那么本次测量就结束了。
但是如果测量模式是 AT_MOST,也就是设置了 MATCH_PARENT 或者设置了具体的值(比如12dp),还得继续取 result 和 size 中的最大值作为 View 最终的宽。
最后返回 result。
View 的宽度测量完毕,高度测量和宽度测量差不多,可以仔细体会下。
四、绘制
绘制就比较简单了,只有两行代码:@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawLine(0, getHeight() / 2.0f, getWidth(), getHeight() / 2.0f, linePaint); canvas.drawRoundRect(new RectF(0, 0, progress * getWidth(), getHeight()), getHeight() / 2.0f, getHeight() / 2.0f, progressPaint); }
先来看第一行代码:
canvas.drawLine(0, getHeight() / 2.0f, getWidth(), getHeight() / 2.0f, linePaint);
通过 canvas 的 drawLine 方法画出中间的线。
前两个参数表示画线的起点
0代表从 x 轴的起点开始画
getHeight() / 2.0f 表示从 y 轴向下 getHeight() / 2.0f 开始画。
后两个参数表示画线的终点
getWidth() 也就是整个 View 的宽度
getHeight() / 2.0f 依旧表示从 y 轴向下 getHeight() / 2.0f 开始画。
再来看第二行代码:
canvas.drawRoundRect(new RectF(0, 0, progress * getWidth(), getHeight()), getHeight() / 2.0f, getHeight() / 2.0f, progressPaint);
第一个参数:
//表示整个View的所在的矩形 new RectF(0, 0, progress * getWidth(), getHeight())
第二个参数和第三个参数都是
getHeight() / 2.0f,表示的是 View 在 x 轴和 y 轴上的圆角半径。
最后一个参数是画进度条的画笔。
只有这两行代码就可以实现整个自定义 View 的绘制。
整个核心绘制已经结束了。
但是我们需要在 Java 代码中动态的更改 View 的进度,所以需要在 View 添加 setProgress 方法如下:
/** * 设置进度 * * @param pProgress */ public void setProgress(float pProgress) { if (pProgress > 1) { pProgress = 1; } else if (pProgress < 0) { pProgress = 0; } if (isSmoothProgress) { smoothRun(this.progress, pProgress); } else { this.progress = pProgress; invalidate(); } }
很简单,如果大于 1,那么就绘制最大进度值 1,
如果小于 0,就绘制最小进度值 0.
如果在 0 和 1 之间:
- 设置带动画的滑动就调用 smoothRun 这个方法。
- 如果不带动画,就直接 invalidate 刷新。
看下 smoothRun 方法:
/** * 设置平滑滑动 * * @param currentProgress * @param targetProgress */ private void smoothRun(float currentProgress, float targetProgress) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(currentProgress, targetProgress); valueAnimator.setTarget(this.progress); valueAnimator.setDuration(1000); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { progress = (float) animation.getAnimatedValue(); invalidate(); } }); valueAnimator.start(); }
首先根据传入的当前的进度值和要显示的进度值创建 ValueAnimator 对象。
设置操作的动画属性为 this.progress。
设置动画时长 1000 毫秒。
然后监听动画过程,动态的给 progress 赋值,不断的刷新 View ,达到动画效果。
最后再开始动画。
最后
前面就是绘制一个简单的自定义 View 的全部过程,虽然代码量不多,但是要考虑的东西还是不少的。接下来还会继续把项目中用到的自定义 View 分享出来。代码地址如下:
attrs.xml
LineProgressBar.java
使用实例
你可以通过以下方式关注我:
1. CSDN
2. 掘金
3. 个人博客
相关文章推荐
- 自定义一个简单的viewpagerIndicator
- 创建一个简单的表视图&自定义UITableView的表单元格
- 自定义View实践-一个简单的棋类游戏
- 自定义TextView简单几步制作一个展示消息的滚动条
- 一个简单的Android自定义view详解
- 自定义View时,用到Paint Canvas的一些温故,只有想不到没有做不到(实例 1,画一个简单的Activity并且实现他能实现的)
- 一个简单的自定义View案例
- 简单实现一个自定义view的ProgressBar
- 一个简单的自定义View,可以显示上图下字
- 自定义一个view,并实现最简单的手势识别功能(下)
- 一个简单的自定义View,仿圆形进度条
- 自定义View实战--实现一个清新美观的加载按钮
- 自定义View时,用到Paint Canvas的一些温故,只有想不到没有做不到(实例 1,画一个简单的Activity并且实现他能实现的)
- 自定义View?一起来打造一个圆形进度条吧。
- 自定义View探究-一个简单的垂直上拉下滑View
- Android自定义View分享——一个水平的进度条
- 一个炫酷的仿雷达扫描和扩散效果——自定义View就是这么简单
- 一个简单自定义View控件
- 简单的实现自定义View画一个钟表
- 自定义控件实战-打造一个简约而不简单的ViewPager指示器