美文网首页Android自定义View程序员Android开发
自定义View,带你撸一个带加载功能的按钮

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

作者: Android开发架构 | 来源:发表于2019-07-09 14:24 被阅读96次

    介绍一个带加载功能的按钮控件的实现原理,加载动画来自于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(){
    

    结果效果是这个样子的:

    看来实际的效果与我们想象中的不太一样,原来Drawable在一开始我们并没有设置它的位置

    drawable.setBounds(0, 0, 80, 80)
    

    那么我们应该如何将绘制居中显示文字的旁边?

    用一张草图表示大概是这个样子的:

    image

    中间那部分就是我们想要的位移,通过下面的计算就可以得到所要的位移,而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,结果功能越写越多就变成这样,苦笑〜)

    本文转载微信公众号:Android技术杂货铺

    读者福利限时分享

    Android开发资料+面试架构资料 免费分享 点击链接 即可领取

    《Android架构师必备学习资源免费领取(架构视频+面试专题文档+学习笔记)》

    相关文章

      网友评论

        本文标题:自定义View,带你撸一个带加载功能的按钮

        本文链接:https://www.haomeiwen.com/subject/dtexkctx.html