场景
毛教员有一句话我非常喜欢,从实践中来到实践中去。写这篇文章的原因是我需要做一个带进度条的按钮,而需要一个带进度条的按钮的原因是我要在对话框中做一个配置页面,而配置页面本来空间就已经很多了,为了把排版尽可能的简洁一些,我需要压缩里面的控件数量以提升使用体验;而配置里面有一个按钮点击后执行了个耗时操作,我需要等待这个操作返回才行让用户继续,但是如果用户点击完就像卡死了一样体验肯定是很差的,在对话框上面在加一层进度条也不太好,自然就想到按下按钮的时候在按钮上提示用户进度;
选型思考
我写代码喜欢尽可能简单,如果需要从github或者别的地方找各种各样的开源组件来实现,我是会有很多疑虑的;首先我得评估这个新加入的东西是不是风险可控:我是不是知道内部运行原理,知道如何在出现问题的时候能很快找到对应的解决方法;
大部分的开源组件设计的是相当精巧的,封装度非常高,通用性也非常高;定制组件和使用开源组件的区别就像去商场买成衣和自己找店家定做衣服一样;我们从商城买到的成衣如果有试穿,知道衣服的材质,版型,以及是否适合自己,那么我们对这个衣服就有了相对全面的了解了,完全可以决定自己是否买下它。但是找到一款完全合适自己的衣服是很难的,因为成衣满足的是大部分人的需求,这个大部分是各个细节被平均的大部分,比如同样的版型,可能只有黑色,而你正好喜欢白色,那么这件衣服再合身,你也不一定会买;又或者你喜欢一件款式很喜欢的衣服但是并不合身,那么这款衣服也不是和你;
那么对比来看,开源组件也需要考虑是否是否满足要求以及是否合身。很多人在选取开源组件的时候只考虑是否功能满足要求却不考虑是否合适自己,一股脑放上去之后发现诸多问题。没上线还好,上线之后就悲剧了,很多情况是拿这种情况干着急;
那是不是就不能用开源组件了呢?当然不是。假如你现在就只想实现一个Demo,而里面需要一个功能是你短时间无法实现但是你很需要,那么大可尝试一下;又或者我们今天要做的这个,其实并不属于完全定制,他只是在官方组件的基础上再进行一次封装,逻辑和原理也都相当简单,我们用别人的开源组件,能很快把所有代码都熟悉一遍并看懂,那也没必要自己再写一个;因此,用还是不用,在于是否合适,这并没有固定的选择;
实现思路
既然要好好做,那就按照正规军的流程走一遍;首先要做啥、怎么用、怎么做,再去做实现;
- 要做啥:支持进度显示的按钮
- 怎么用:用户点击按钮后,按钮会根据后台任务的进度更新按钮上提示的进度,进度条方式,后台执行结束,进度条打满;
- 怎么做:继承官方提供的Buttom组件,在view初始化时初始化需要显示的进度View,并在OnDraw绘制的时候添加更新进度View的代码逻辑,将添加的进度绘制到canvas上;暴露设置进度和设置进度颜色的方法给外部调用;用户在使用这个view 的时候可以直接在布局文件中配置,也可以在代码中配置;
具体实现
这个例子比较简单,我得解释代码就直接写在代码中了,相信大家一看便知;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatButton;
import androidx.core.content.ContextCompat;
public class ProgressButton extends AppCompatButton {
/**
* 圆角幅度
*/
private float mCornerRadius = 0;
private float mProgressMargin = 0;
/**
* 是否进度条已走完
*/
private boolean mFinish;
/**
* 当前进度值
*/
private int mProgress;
/**
* 最大进度值
*/
private int mMaxProgress = 100;
/**
* 最小进度值
*/
private int mMinProgress = 0;
private GradientDrawable mDrawableButton;
private GradientDrawable mDrawableProgressBackground;
private GradientDrawable mDrawableProgress;
public ProgressButton(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public ProgressButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs);
}
/**
* 自定义简单组件的必须流程,在这里接收传入的自定义属性,完成各属性值和数据的初始化
* @param context 上下文
* @param attrs 自定义属性值
*/
private void initialize(Context context, AttributeSet attrs) {
//进度条背景绘制
mDrawableProgressBackground = new GradientDrawable();
//进度绘制
mDrawableProgress = new GradientDrawable();
//按钮绘制
mDrawableButton = new GradientDrawable();
//默认的按钮颜色,如果自定义属性未设置则使用该默认值,下同
int defaultButtonColor = ContextCompat.getColor(context, R.color.gray);
//默认的进度条颜色
int defaultProgressColor = ContextCompat.getColor(context, R.color.green);
//默认的进度背景颜色,不要和进度条颜色一样,不然无法区分当前进度
int defaultBackColor = ContextCompat.getColor(context, R.color.gray);
// 下面一直到finally都是从传入的attr读取配置并设置到全局变量
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ProgressButton);
try {
mProgressMargin = attr.getDimension(R.styleable.ProgressButton_progressMargin, mProgressMargin);
mCornerRadius = attr.getDimension(R.styleable.ProgressButton_cornerRadius, mCornerRadius);
int buttonColor = attr.getColor(R.styleable.ProgressButton_buttonColor, defaultButtonColor);
mDrawableButton.setColor(buttonColor);
int progressBackColor = attr.getColor(R.styleable.ProgressButton_progressBackColor, defaultBackColor);
mDrawableProgressBackground.setColor(progressBackColor);
int progressColor = attr.getColor(R.styleable.ProgressButton_progressColor, defaultProgressColor);
mDrawableProgress.setColor(progressColor);
mProgress = attr.getInteger(R.styleable.ProgressButton_progress, mProgress);
mMinProgress = attr.getInteger(R.styleable.ProgressButton_minProgress, mMinProgress);
mMaxProgress = attr.getInteger(R.styleable.ProgressButton_maxProgress, mMaxProgress);
} finally {
attr.recycle();
}
//设置button的圆角
mDrawableButton.setCornerRadius(mCornerRadius);
mDrawableProgressBackground.setCornerRadius(mCornerRadius);
mDrawableProgress.setCornerRadius(mCornerRadius - mProgressMargin);
setBackgroundDrawable(mDrawableButton);
mFinish = false;
}
@Override
protected void onDraw(Canvas canvas) {
if (mProgress > mMinProgress && mProgress <= mMaxProgress && !mFinish) {
//计算进度条的宽度
float progressWidth =
(float) getMeasuredWidth() * ((float) (mProgress - mMinProgress) / mMaxProgress - mMinProgress);
//如果进度条宽度比圆形按钮还宅,那肯定是设置的有问题,使用园直径
if (progressWidth < mCornerRadius * 2) {
progressWidth = mCornerRadius * 2;
}
//设置进度条的边界区域在除去margin的整个控件以内
mDrawableProgress.setBounds((int) mProgressMargin, (int) mProgressMargin,
(int) (progressWidth - mProgressMargin), getMeasuredHeight() - (int) mProgressMargin);
//将进度绘制到画布canvas
mDrawableProgress.draw(canvas);
if (mProgress == mMaxProgress) {
setBackgroundDrawable(mDrawableButton);
mFinish = true;
}
}
super.onDraw(canvas);
}
/**
* 设置当前进度
*/
public void setProgress(int progress) {
if (!mFinish) {
mProgress = progress;
setBackgroundDrawable(mDrawableProgressBackground);
// 设置完要invalidate更新一下才会立即调用绘制
invalidate();
}
}
public void setMaxProgress(int maxProgress) {
mMaxProgress = maxProgress;
}
public void setMinProgress(int minProgress) {
mMinProgress = minProgress;
}
public void reset() {
mFinish = false;
mProgress = mMinProgress;
}
}
我们用刀了自定义属性,需要在value的attr.xml中申明一下;我们这里支持8种自定义属性,当然你可根据你项目的实际需求增加或者减少数量;
- progressColor 进度条颜色
- progressBackColor 进度条背景
- buttonColor 按钮颜色
- cornerRadius 按钮圆角大小
- progress 当前进度
- minProgress 最小进度
- maxProgress 最大进度
- progressMargin 进度条相对总View的margin
<declare-styleable name="ProgressButton">
<attr name="progressColor" format="color" />
<attr name="progressBackColor" format="color" />
<attr name="buttonColor" format="color" />
<attr name="cornerRadius" format="dimension" />
<attr name="progress" format="integer" />
<attr name="minProgress" format="integer" />
<attr name="maxProgress" format="integer" />
<attr name="progressMargin" format="dimension" />
</declare-styleable>
接下来我们就能在布局文件中使用了;
<com.larson.view.ProgressButton
...
android:textSize="15sp"
app:buttonColor="#80cc33"
app:cornerRadius="16dp"
app:progress="0"
app:progressBackColor="#e0e0e0"
app:progressColor="#80cc33"
app:progressMargin="2dp"/>
总结
实现很简单,基本没啥技术含量,文章开头花了很长时间罗里吧嗦的讲了一些我自己的研发技巧。希望对大家能有所启发,也欢迎大家拍砖,后续我尽量把博客写成一系列,方便大家查阅。