Android自定义控件之带下载进度的下载按钮DownloadProgressButton

前言

最近要用到一个带下载进度的按钮,各种搜索了一波,很抱歉(/TДT)/ ,实在没有发现自己想要的效果,没办法只能自己尝试实现了一个了。

效果展示

demo

  1. 支持圆角,支持是否显示边框

  2. 中间文字会根据下载进度有个变色的效果

  3. 下载完成后,“安装中“有一个 loading 的动画效果

  4. 继承自TextView,自带设置字体的功能

难点分析

1 . 怎样实现中间文字会根据下载进度有个变色的效果?

可以通过 LinearGradient 实现字体渐变的效果,参考了LinearGradient与闪动文字效果,具体在代码中解释。

2 . 按钮的背景根据进度不断铺满?

以下图为例,根据当前进度画出一个和整个按钮相同大小的蓝色矩形( dst ),再画一个同样大小的白色矩形,只显示白色矩形中两个重叠的部分不就好了吗?对,我们联想到了两个图层重叠时的显示模式,就是这里的 SRC_ATOP 模式。

3 . “安装中“有一个 loading 的动画效果?

网上已经有很多成熟的 loading 动画效果,理解一下它们的源码,自己实现一下就可以了,我这里参考了AVLoadingIndicatorView 中的两个不错的动画效果,当然你也可以修改成你自己喜欢的效果。

实现

来到了贴代码时间,O(∩_∩)O,其实就是按照自定义控件的基本流程

1 . 定义自定义属性

PropertyFormatDefault
progress_btn_radiusdimension0dp
progress_btn_background_colorcolor3385FF
progress_btn_background_second_colorcolorE8E8E8
progress_btn_text_colorcolorprogress_btn_background_color
progress_btn_text_cover_colorcolorColor.WHITE
progress_btn_border_widthdimension2dp
progress_btn_ball_styleenumSTYLE_BALL_JUMP

STYLE_BALL_PULSE

STYLE_BALL_PULSE

STYLE_BALL_JUMP

STYLE_BALL_JUMP

这张图可能更加直观吧:

2 . 绘制按钮背景(根据不同状态绘制不同的效果)

    private void drawBackground(Canvas canvas) {

        mBackgroundBounds = new RectF();
        //根据Border宽度得到Button的显示区域
        mBackgroundBounds.left = showBorder ? mBorderWidth : 0;
        mBackgroundBounds.top = showBorder ? mBorderWidth : 0;
        mBackgroundBounds.right = getMeasuredWidth() - (showBorder ? mBorderWidth : 0);
        mBackgroundBounds.bottom = getMeasuredHeight() - (showBorder ? mBorderWidth : 0);

        if (showBorder) {
            mBackgroundPaint.setStyle(Paint.Style.STROKE);
            mBackgroundPaint.setColor(mBackgroundColor);
            mBackgroundPaint.setStrokeWidth(mBorderWidth);
            canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
        }
        mBackgroundPaint.setStyle(Paint.Style.FILL);
        //color
        switch (mState) {
            case STATE_NORMAL:
                mBackgroundPaint.setColor(mBackgroundColor);
                canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
                break;
            case STATE_PAUSE:
            case STATE_DOWNLOADING:
                //计算当前的进度
                mProgressPercent = mProgress / (mMaxProgress + 0f);
                mBackgroundPaint.setColor(mBackgroundSecondColor);
                canvas.save();
                //画出dst图层
                canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
                //设置图层显示模式为 SRC_ATOP
                PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP);
                mBackgroundPaint.setColor(mBackgroundColor);
                mBackgroundPaint.setXfermode(porterDuffXfermode);
                //计算 src 矩形的右边界
                float right = mBackgroundBounds.right * mProgressPercent;
                //在dst画出src矩形
                canvas.drawRect(mBackgroundBounds.left, mBackgroundBounds.top, right, mBackgroundBounds.bottom, mBackgroundPaint);
                canvas.restore();
                mBackgroundPaint.setXfermode(null);
                break;
            case STATE_FINISH:
                mBackgroundPaint.setColor(mBackgroundColor);
                canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
                break;
        }
    }
   
   

    3 . 绘制文字

      private void drawTextAbove(Canvas canvas) {
            //计算Baseline绘制的Y坐标
            final float y = canvas.getHeight() / 2 - (mTextPaint.descent() / 2 + mTextPaint.ascent() / 2);
            if (mCurrentText == null) {
                mCurrentText = "";
            }
            final float textWidth = mTextPaint.measureText(mCurrentText.toString());
            mTextBottomBorder = y;
            mTextRightBorder = (getMeasuredWidth() + textWidth) / 2;
            //color
            switch (mState) {
                case STATE_NORMAL:
                    mTextPaint.setShader(null);
                    mTextPaint.setColor(mTextCoverColor);
                    canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
                    break;
                case STATE_PAUSE:
                case STATE_DOWNLOADING:
    
                    //进度条压过距离
                    float coverLength = getMeasuredWidth() * mProgressPercent;
                    //开始渐变指示器
                    float indicator1 = getMeasuredWidth() / 2 - textWidth / 2;
                    //结束渐变指示器
                    float indicator2 = getMeasuredWidth() / 2 + textWidth / 2;
                    //文字变色部分的距离
                    float coverTextLength = textWidth / 2 - getMeasuredWidth() / 2 + coverLength;
                    float textProgress = coverTextLength / textWidth;
                    if (coverLength <= indicator1) {
                        mTextPaint.setShader(null);
                        mTextPaint.setColor(mTextColor);
                    } else if (indicator1 < coverLength && coverLength <= indicator2) {
                        //设置变色效果
                        mProgressTextGradient = new LinearGradient((getMeasuredWidth() - textWidth) / 2, 0, (getMeasuredWidth() + textWidth) / 2, 0,
                                new int[]{mTextCoverColor, mTextColor},
                                new float[]{textProgress, textProgress + 0.001f},
                                Shader.TileMode.CLAMP);
                        mTextPaint.setColor(mTextColor);
                        mTextPaint.setShader(mProgressTextGradient);
                    } else {
                        mTextPaint.setShader(null);
                        mTextPaint.setColor(mTextCoverColor);
                    }
                    canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
                    break;
                case STATE_FINISH:
                    mTextPaint.setColor(mTextCoverColor);
                    canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
                    drawLoadingBall(canvas);
                    break;
    
            }
    
        }
       
       

      这里使用了 LinearGradient 的第二个构造函数:
      public LinearGradient(float x0, float y0, float x1, float y1,int colors[], float positions[], TileMode tile)
      (x0,y0) 就是起始渐变点坐标,参数中 (x1,y1) 就是结束渐变点坐标
      colors[] 用于指定渐变的颜色值数组,同样,颜色值必须使用 0xAARRGGBB 形式的16进制表示!表示透明度的AA一定不能少。
      positions[]与渐变的颜色相对应,取值是0-1的float类型,表示在每一个颜色在整条渐变线中的百分比位置,之间的差值就是渐变区间。

      4 . 两种动画效果

      STYLE_BALL_PULSE

       public ArrayList<ValueAnimator> createBallPulseAnimators() {
              ArrayList<ValueAnimator> animators = new ArrayList<>();
              int[] delays = new int[]{120, 240, 360};
              for (int i = 0; i < 3; i++) {
                  final int index = i;
      
                  ValueAnimator scaleAnim = ValueAnimator.ofFloat(1, 0.3f, 1);
      
                  scaleAnim.setDuration(750);
                  scaleAnim.setRepeatCount(-1);
                  scaleAnim.setStartDelay(delays[i]);
      
                  scaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                      @Override
                      public void onAnimationUpdate(ValueAnimator animation) {
                          scaleFloats[index] = (float) animation.getAnimatedValue();
                          postInvalidate();
                      }
                  });
                  animators.add(scaleAnim);
              }
              return animators;
          }
         
         

        STYLE_BALL_JUMP

         public ArrayList<ValueAnimator> createBallJumpAnimators() {
                ArrayList<ValueAnimator> animators = new ArrayList<>();
                int[] delays = new int[]{70, 140, 210};
                for (int i = 0; i < 3; i++) {
                    final int index = i;
                    ValueAnimator scaleAnim = ValueAnimator.ofFloat(mTextBottomBorder, mTextBottomBorder - mBallRadius * 2, mTextBottomBorder);
                    scaleAnim.setDuration(600);
                    scaleAnim.setRepeatCount(-1);
                    scaleAnim.setStartDelay(delays[i]);
                    scaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            translateYFloats[index] = (float) animation.getAnimatedValue();
                            postInvalidate();
                        }
                    });
                    animators.add(scaleAnim);
                }
                return animators;
            }
        
           
           

          总结

          有什么不明白的可以下载完整源码学习一下,ps:自己也是站在巨人的肩膀上,才能够完整实现这个自定义控件,很感谢那些乐于分享和开源的大神,也希望可以帮助有需要的同学!
          如果您觉得对你有所帮助,轻轻点一下star,来鼓励一下我,谢谢!o(∩_∩)o

          源码下载

          参考

          LinearGradient与闪动文字效果

          Paint之setXfermode(一)

          安卓自定义View教程目录

          Android 自定义view之FontMetric

          • 0
            点赞
          • 0
            收藏
            觉得还不错? 一键收藏
          • 0
            评论

          “相关推荐”对你有帮助么?

          • 非常没帮助
          • 没帮助
          • 一般
          • 有帮助
          • 非常有帮助
          提交
          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值