|
| 1 | +## 简介 |
| 2 | +--- |
| 3 | +介绍一个带加载功能的按钮控件的实现原理,加载动画来自于[CircularProgressDrawable](https://developer.android.google.cn/reference/android/support/v4/widget/CircularProgressDrawable?hl=en) |
| 4 | +<br/> |
| 5 | +## 效果图(最终效果图在最后面) |
| 6 | +--- |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +<br/> |
| 11 | + |
| 12 | +## 下面开始介绍实现的原理 |
| 13 | +--- |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +#### 加载动画 |
| 19 | +圆环加载就是用setCompoundDrawables放到TextView的drawablewStart中,将文字的Gravity设置Center |
| 20 | +``` |
| 21 | +public class DrawableText extends AppCompatTextView { |
| 22 | + ..... 省略 |
| 23 | +
|
| 24 | +
|
| 25 | + private void init(){ |
| 26 | + mProgressDrawable = new CircularProgressDrawable(getContext()); |
| 27 | + mProgressDrawable.setColorSchemeColors(getTextColors().getDefaultColor()); |
| 28 | + mProgressDrawable.setBounds(0, 0, 80, 80); |
| 29 | + setCompoundDrawables(mProgressDrawable, null, null, null); |
| 30 | + mProgressDrawable.setStrokeWidth(10); |
| 31 | + } |
| 32 | +
|
| 33 | + public void start(){ |
| 34 | + mProgressDrawable.start(); |
| 35 | + } |
| 36 | +
|
| 37 | + public void stop(){ |
| 38 | + mProgressDrawable.stop(); |
| 39 | + } |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +结果效果是这个亚子的: |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | +看来实际的效果与我们想象中的不太一样,原来Drawable在一开始我们并没有设置它的位置 |
| 49 | +``` |
| 50 | +drawable.setBounds(0, 0, 80, 80) |
| 51 | +``` |
| 52 | +<br/> |
| 53 | +<br/> |
| 54 | + |
| 55 | +那么我们应该如何将drawable居中显示文字的旁边? |
| 56 | +用一张草图表示大概是这个样子的: |
| 57 | + |
| 58 | + |
| 59 | +中间那部分就是我们想要的位移,通过下面的计算就可以得到所要的位移, |
| 60 | +而getWidth()这些参数需要在Layout之后才可以得到, |
| 61 | +所以我们干脆在onDraw中对drawable进行位移 |
| 62 | +``` |
| 63 | + //计算需要的位移 |
| 64 | + private float calcOffset() { |
| 65 | + //getCompoundPaddingStart() = paddingStart + drawableWidth + drawablePadding |
| 66 | + return (getWidth() - (getCompoundPaddingStart() + getTextWidth())) / 2; |
| 67 | +
|
| 68 | + } |
| 69 | +
|
| 70 | + //计算文字的长度 |
| 71 | + private float getTextWidth() { |
| 72 | + //在draw时不断计算TextWidth似乎是不合理的,当然目前只是演示用法 |
| 73 | + //再者若是多行文字,则测量结果会偏大,但此处不再讨论,有兴趣可以直接去看源码 |
| 74 | + return getPaint().measureText(getText().toString()); |
| 75 | + } |
| 76 | +``` |
| 77 | + |
| 78 | + |
| 79 | +``` |
| 80 | +
|
| 81 | + private void init(){ |
| 82 | + mProgressDrawable = new CircularProgressDrawable(getContext()); |
| 83 | + mProgressDrawable.setColorSchemeColors(getTextColors().getDefaultColor()); |
| 84 | + mProgressDrawable.setBounds(0, 0, 80, 80); |
| 85 | + //先保存Bounds |
| 86 | + bounds = mProgressDrawable.copyBounds(); |
| 87 | + setCompoundDrawables(mProgressDrawable, null, null, null); |
| 88 | + mProgressDrawable.setStrokeWidth(10); |
| 89 | + } |
| 90 | +
|
| 91 | + @Override |
| 92 | + protected void onDraw(Canvas canvas) { |
| 93 | + final int offsetX = (int) calcOffsetX(); |
| 94 | + mProgressDrawable.setBounds(offsetX, bounds.top, bounds.right + offsetX, bounds.bottom); |
| 95 | + //我们并不能通过offset来直接位移mProgressDrawable,这样为导致动画每次绘制时都会在原来位移过后的基础上再不断向右位移 |
| 96 | + //mProgressDrawable.getBounds().offset(offsetX,0); |
| 97 | + super.onDraw(canvas); |
| 98 | + } |
| 99 | +
|
| 100 | +``` |
| 101 | +#### 经过位移的效果 |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | +似乎看起来还是有一点点别扭,从效果上看出文字和drawable是一起居中的, |
| 106 | +看了一下TextView的源码发现setCompoundDrawables后会先划分出TextView左侧及右侧drawable需要的空间, |
| 107 | +然后再按照剩余的空间来居中显示, |
| 108 | +所以最后得到通过位移得到的效果是文字和drawable一起居中显示的。 |
| 109 | + |
| 110 | +为了让文字在整个布局的中间,我们可以通过平移画布来实现文字的居中效果 |
| 111 | +``` |
| 112 | +
|
| 113 | + @Override |
| 114 | + protected void onDraw(Canvas canvas) { |
| 115 | + final int offsetX = (int) calcOffsetX(); |
| 116 | + mProgressDrawable.setBounds(offsetX, bounds.top, bounds.right + offsetX, bounds.bottom); |
| 117 | + //我们并不能通过offset来直接位移mProgressDrawable,这样为导致动画每次绘制时都会不断向右位移 |
| 118 | + //mProgressDrawable.getBounds().offset(offsetX,0); |
| 119 | +
|
| 120 | + //计算画布向左平移的距离 |
| 121 | + final int tranX = (bounds.width() + getCompoundDrawablePadding()) / 2; |
| 122 | + canvas.translate(-tranX, 0); |
| 123 | +
|
| 124 | + super.onDraw(canvas); |
| 125 | + } |
| 126 | +
|
| 127 | +``` |
| 128 | +#### 我们可以看到,文字也居中了 |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | +#### 最后再说一下收缩效果的实现方式 |
| 133 | +主要也是通过 getLayoutParams().width和 getLayoutParams().height来改变布局的尺寸, |
| 134 | +在开始收缩时先将文本设置为空字符、drawablePadding设为0,然后再开始收缩动画,具体的方式可以自行尝试 |
| 135 | +``` |
| 136 | + mShrinkAnimator = ValueAnimator.ofFloat(0, 1f); |
| 137 | + mShrinkAnimator.setDuration(mShrinkDuration); |
| 138 | + mShrinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 139 | + @Override |
| 140 | + public void onAnimationUpdate(ValueAnimator animation) { |
| 141 | + // mRootViewSizeSaved是预先保存原先的尺寸,getShrinkSize() 是缩放后的尺寸 |
| 142 | + // b = getRootViewSize() |
| 143 | + // k = getRootViewSize() - getLoadingSize |
| 144 | + getLayoutParams().width = (int) ((getShrinkSize() - mRootViewSizeSaved[0]) * (float) animation.getAnimatedValue() + mRootViewSizeSaved[0]); |
| 145 | + getLayoutParams().height = (int) ((getShrinkSize() - mRootViewSizeSaved[1]) * (float) animation.getAnimatedValue() + mRootViewSizeSaved[1]); |
| 146 | + requestLayout(); |
| 147 | + } |
| 148 | + }); |
| 149 | +``` |
| 150 | +<br/><br/> |
| 151 | + |
| 152 | +# 结语 |
| 153 | +--- |
| 154 | +本文介绍了带加载效果的按钮实现整体思路,然鹅如果想要真正使用并没有文中介绍的那么简单,还需要考虑各种细节和因素。(头发又变少了呢~) |
| 155 | +最后可以看下完整实现的效果,已经上传到github上了([LoadingButton](https://github.com/FlodCoding/LoadingButton)),加了一些功能(本来只是想简单实现一个按钮旁边有一个Loading,结果功能越写越多就变成这样,苦笑~) |
| 156 | +有兴趣朋友可以给个星星,提提issue喝喝茶,我是新来的第一次写这种文章请多多包涵呀。 |
| 157 | + |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | + |
0 commit comments