查看原文
其他

自定义View,带你撸一个带加载功能的按钮

码农西哥 技术最TOP 2022-08-27


本文来自没有昵称投稿文章地址:https://www.jianshu.com/p/68044f728c46

介绍一个带加载功能的按钮控件的实现原理,加载动画来自于CircularProgressDrawable

效果图(最终效果图在最后面)

实现原理

加载圆环就是用setCompoundDrawables放到TextViewdrawablewStart中,文字将的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(){
mProgressDrawable.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().widthgetLayoutParams().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,结果功能越写越多就变成这样,苦笑〜)

有兴趣朋友可以给个明星,提提问题喝喝茶,我是新来的第一次写这种文章请多多包涵呀。

---end---

推荐阅读:

自定义Layout,让子View支持圆角属性

牛逼了,轻松实现RecyclerView 拖动多选功能

Android 实现滴滴出行App功能

每一个“在看”,我都当成真的喜欢

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存