自定义View,带你撸一个带加载功能的按钮
介绍一个带加载功能的按钮控件的实现原理,加载动画来自于CircularProgressDrawable
效果图(最终效果图在最后面)
实现原理
加载圆环就是用setCompoundDrawables
放到TextView
的drawablewStart
中,文字将的Gravity
设置Center
public class DrawableText extends AppCompatTextView {
..... 省略
private void init(){
mProgressDrawable = new CircularProgressDrawable(getContext());
mProgressDrawable.setColorSchemeColors(getTextColors().getDefaultColor());
mProgressDrawable.setBounds(0, 0, 80, 80);
setCompoundDrawables(mProgressDrawable, null, null, null);
mProgressDrawable.setStrokeWidth(10);
}
public void start(){
mProgressDrawable.start();
}
public void stop(){
结果效果是这个样子的:
看来实际的效果与我们想象中的不太一样,原来Drawable
在一开始我们并没有设置它的位置
drawable.setBounds(0, 0, 80, 80)
那么我们应该如何将绘制居中显示文字的旁边?
用一张草图表示大概是这个样子的:
中间那部分就是我们想要的位移,通过下面的计算就可以得到所要的位移,而getWidth()
这些参数需要在布局之后才可以得到,所以我们干脆在onDraw
中对drawable
进行位移。
//计算需要的位移
private float calcOffset() {
//getCompoundPaddingStart() = paddingStart + drawableWidth + drawablePadding
return (getWidth() - (getCompoundPaddingStart() + getTextWidth())) / 2;
}
//计算文字的长度
private float getTextWidth() {
//在draw时不断计算TextWidth似乎是不合理的,当然目前只是演示用法
//再者若是多行文字,则测量结果会偏大,但此处不再讨论,有兴趣可以直接去看源码
return getPaint().measureText(getText().toString());
}
private void init(){
mProgressDrawable = new CircularProgressDrawable(getContext());
mProgressDrawable.setColorSchemeColors(getTextColors().getDefaultColor());
mProgressDrawable.setBounds(0, 0, 80, 80);
//先保存Bounds
bounds = mProgressDrawable.copyBounds();
setCompoundDrawables(mProgressDrawable, null, null, null);
mProgressDrawable.setStrokeWidth(10);
}
@Override
protected void onDraw(Canvas canvas) {
final int offsetX = (int) calcOffsetX();
mProgressDrawable.setBounds(offsetX, bounds.top, bounds.right + offsetX, bounds.bottom);
//我们并不能通过offset来直接位移mProgressDrawable,这样为导致动画每次绘制时都会在原来位移过后的基础上再不断向右位移
//mProgressDrawable.getBounds().offset(offsetX,0);
super.onDraw(canvas);
}
经过位移的效果
似乎看起来还是有一点点别扭,效果从上看出文字状语从句:drawable
的英文一起居中的,一下看了TextView
的源码发现setCompoundDrawables
后会先划分出TextView
左侧及右侧drawable
需要的空间,然后再按照剩余的空间来居中显示,所以得到求最后通过位移得到的效果的英文文字状语从句:drawable
一起居中显示的。
为了让文字在整个布局的中间,我们可以通过平移画布来实现文字的居中效果。
@Override
protected void onDraw(Canvas canvas) {
final int offsetX = (int) calcOffsetX();
mProgressDrawable.setBounds(offsetX, bounds.top, bounds.right + offsetX, bounds.bottom);
//我们并不能通过offset来直接位移mProgressDrawable,这样为导致动画每次绘制时都会不断向右位移
//mProgressDrawable.getBounds().offset(offsetX,0);
//计算画布向左平移的距离
final int tranX = (bounds.width() + getCompoundDrawablePadding()) / 2;
canvas.translate(-tranX, 0);
super.onDraw(canvas);
}
我们可以看到,文字也居中了:
最后再说一下收缩效果的实现方式
主要也是通过getLayoutParams().width
和getLayoutParams().height
来改变布局的尺寸,在开始收缩时先将文本设置为空drawablePadding
字符0
,设为,然后再开始收缩动画,具体的方式可以自行尝试。
mShrinkAnimator = ValueAnimator.ofFloat(0, 1f);
mShrinkAnimator.setDuration(mShrinkDuration);
mShrinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// mRootViewSizeSaved是预先保存原先的尺寸,getShrinkSize() 是缩放后的尺寸
// b = getRootViewSize()
// k = getRootViewSize() - getLoadingSize
getLayoutParams().width = (int) ((getShrinkSize() - mRootViewSizeSaved[0]) * (float) animation.getAnimatedValue() + mRootViewSizeSaved[0]);
getLayoutParams().height = (int) ((getShrinkSize() - mRootViewSizeSaved[1]) * (float) animation.getAnimatedValue() + mRootViewSizeSaved[1]);
requestLayout();
}
});
最终效果图:
结语
本文介绍了带加载效果的按钮实现整体思路,然鹅如果想要真正使用并没有文中介绍的那么简单,还需要考虑各种细节和因素。(头发又变少了呢〜)
最后可以看下完整实现的效果,已经上传到github上了(LoadingButton),加了一些功能(本来只是想简单实现一个按钮旁边有一个Loading,结果功能越写越多就变成这样,苦笑〜)
本文转载微信公众号:Android技术杂货铺