自定义多状态高仿应用下载百分数view

说在前面

话说 很多的应用下载中心就有一个下载的过程,然后呢就需要用一个图标来显示下载的状态。
不多说,看图说话:

从图上看下载都会有这么几种状态:未开始、正在下载、下载暂停、下载完成。然后还可能出现的状况就是下载出错和等待下载是无法从图里面体现的。

实现思路

实现思路:你看下载的状态是通过几张图片来给我们不同的视觉,确切的是5种状态,应用没有下载之前是下载状态,加入下载列表后没有下载的时候是等待状态,下载过程中会显示进度数字被分数加载变化和进度条变化,点击后出现的暂停状态并且显示进度,下载完成后是完成的状态。

这5种状态下:只有暂停状态和正在下载状态需要我们draw圆环和文字,其他情况下只需要drawImage就可以了,然后这里使用的是自定义属性来实现这个view,通过handle来刷新下载状态从而 实现ui的刷新。

代码的具体实现

自定义 属性文件的书写:

先新建attrs.xml文件,里面代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DownloadPercentView">
        <attr name="radius" format="dimension"/>
        <attr name="notBeginImg" format="string"/>
        <attr name="waitImg" format="string"/>
        <attr name="pausedImg" format="string"/>
        <attr name="finishedImg" format="string"/>
        <attr name="strokeWidth" format="dimension"/>
        <attr name="circleColor" format="color"/>
        <attr name="ringColor" format="color"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
    </declare-styleable>
</resources>

属性解析:

  • radius就是我们所要显的控件的半径
  • notBeginImg 没有开始下载的图片
  • waitImg 等待下载的图片
  • pausedImg 按下暂停时的图片
  • finishedImg 下载完成时的图片
  • strokeWidth 圈画笔的宽度
  • circleColor 圆环默认颜色
  • ringColor 进度展示的颜色
  • textColor 进度文字颜色
  • textSize 进度文字尺寸

自定义view代码的书写

步骤一:
获取自定义属性:

/** * 初始化自定义属性 * @param context * @param attrs */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.DownloadPercentView, 0, 0);
        mRadius = (int)typeArray.getDimension(R.styleable.DownloadPercentView_radius, 100);
        mStrokeWidth = (int)typeArray.getDimension(R.styleable.DownloadPercentView_strokeWidth, 2);
        mCircleColor = typeArray.getColor(R.styleable.DownloadPercentView_circleColor, 0xFFFFFFFF);
        mRingColor = typeArray.getColor(R.styleable.DownloadPercentView_ringColor, 0xFFFFFFFF);
        mTextColor = typeArray.getColor(R.styleable.DownloadPercentView_textColor, 0xFFFFFFFF);
        mNotBeginImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_notBeginImg)).getBitmap();
        mPausedImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_pausedImg)).getBitmap();
        mWatiImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_waitImg)).getBitmap();
        finishedImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_finishedImg)).getBitmap();
        mTextSize = (int)typeArray.getDimension(R.styleable.DownloadPercentView_textSize, 28);
        //记住recycle,并避免内存泄漏
        typeArray.recycle();
    }

计算控件size:

/** * 计算控件的宽高 * @param widthMeasureSpec * @param heightMeasureSpec */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = (int)Math.ceil(mRadius) * SCALE;
        setMeasuredDimension(width, width);
    }

初始化画笔:

/** * * 初始化画文字的画笔 * @param mTxtPaint */
    private void initTextPaint(Paint mTxtPaint) {
        mTxtPaint.setAntiAlias(true);
        mTxtPaint.setColor(mTextColor);//Color.parseColor("#52ce90")
        mTxtPaint.setTextAlign(Paint.Align.CENTER);
        mTxtPaint.setTextSize(mTextSize);
    }

    /** * 初始化圈和弧的画笔 * @param targetPaint 区分画笔 * @param color 画笔颜色 */

    private void initPaint(Paint targetPaint,int color) {

        targetPaint.setAntiAlias(true);
        targetPaint.setColor(color);
        targetPaint.setStyle(Paint.Style.STROKE);
        targetPaint.setStrokeWidth(mStrokeWidth);
    }
  private void initVariable() {
        mCirclePaint = new Paint();
        mRingPaint= new Paint();
        mTxtPaint = new Paint();
        //初始化绘制灰色圆的画笔
        initPaint(mCirclePaint,mCircleColor);
        //初始化绘制圆弧的画笔
        initPaint(mRingPaint,mRingColor);
        //初始化绘制文字的画笔
        initTextPaint(mTxtPaint);
        //初始化要显示的图片
        initImage();
    }

接下来我们写onDraw:

@Override
    protected void onDraw(Canvas canvas) {
        //控件的中心坐标
        mXCenter = getWidth() / SCALE;
        mYCenter = getHeight() / SCALE;
        switch (mStatus) {
            case STATUS_NOBEGIN://原始状态
                canvas.drawBitmap(mNotBeginImg, 0, 0, null);
                break;
            case STATUS_WAITING://等待下载状态
                canvas.drawBitmap(mWatiImg, 0, 0, null);
                break;
            case STATUS_DOWNLOADING://下载中
                drawDownloadingView(canvas);
                break;
            case STATUS_PAUSED://暂停
                drawPausedView(canvas);
                break;
            case STATUS_FINISHED://下载完成
                canvas.drawBitmap(finishedImg, 0, 0, null);
                break;
        }

看代码就知道:只有暂停和下载过程需要我们去画进度:

下载过程:(需要画出灰色圆圈,接下来根据进度绘制进度和文字)

    /** * 绘制下载中的view * @param canvas */
    private void drawDownloadingView(Canvas canvas) {
        //绘制灰色圆环
        canvas.drawCircle(mXCenter, mYCenter, mRadius - mStrokeWidth/SCALE, mCirclePaint);
        //绘制进度扇形圆环
        RectF oval = new RectF();
        //设置椭圆上下左右的坐标
        oval.left = mXCenter - mRadius + mStrokeWidth/SCALE;
        oval.top = mYCenter - mRadius + mStrokeWidth/SCALE;
        oval.right = mXCenter + mRadius - mStrokeWidth/SCALE;
        oval.bottom = mYCenter + mRadius - mStrokeWidth/SCALE;
        canvas.drawArc(oval, -90, ((float)mProgress / mTotalProgress) * 360, false, mRingPaint);//false顺势针
        //绘制中间百分比文字
        String percentTxt = String.valueOf(mProgress);
        //计算文字垂直居中的baseline
        Paint.FontMetricsInt fontMetrics = mTxtPaint.getFontMetricsInt();
        float baseline = oval.top + (oval.bottom - oval.top - fontMetrics.bottom + fontMetrics.top) / SCALE - fontMetrics.top;
        canvas.drawText(percentTxt+"%", mXCenter, baseline, mTxtPaint);
    }

步骤就是先画出灰色圆圈,然后根据 进度画出扇形区域,最后就是根据进度显示百分数的文字。

暂停过程的绘制:(同上,只不过不文字换成暂停图片)

    /** * 绘制暂停时的view * @param canvas */
    private void drawPausedView(Canvas canvas) {
        //绘制灰色圆环
        canvas.drawCircle(mXCenter, mYCenter, mRadius - mStrokeWidth/SCALE, mCirclePaint);
        //绘制进度扇形圆环
        RectF oval = new RectF();
        //设置椭圆上下左右的坐标
        oval.left = mXCenter - mRadius + mStrokeWidth/SCALE;
        oval.top = mYCenter - mRadius + mStrokeWidth/SCALE;
        oval.right = mXCenter + mRadius - mStrokeWidth/SCALE;
        oval.bottom = mYCenter + mRadius - mStrokeWidth/SCALE;
        canvas.drawArc(oval, -90, ((float) mProgress / mTotalProgress) * 360, false, mRingPaint);
        //绘制中间暂停图标
        canvas.drawBitmap(mPausedImg, 0, 0, null);
    }

现在贴出自定义view完整代码:

package com.losileeya.downloadpercent_master;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.View;

/** * @Class: DownloadPercentView */
public class DownloadPercentView extends View {
    //状态设置
    public final static int STATUS_NOBEGIN= 1;
    public final static int STATUS_WAITING = 2;
    public final static int STATUS_DOWNLOADING = 3;
    public final static int STATUS_PAUSED = 4;
    public final static int STATUS_FINISHED = 5;
    // 画实心圆的画笔
    private Paint mCirclePaint=null;
    // 画圆环的画笔
    private Paint mRingPaint=null;
    // 绘制进度文字的画笔
    private Paint mTxtPaint=null;
    // 圆形颜色
    private int mCircleColor;
    // 圆环颜色
    private int mRingColor;
    //文字颜色
    private int mTextColor;
    //文字大小
    private int mTextSize;
    // 半径
    private int mRadius;
    // 圆环宽度
    private int mStrokeWidth = 2;
    // 圆心x坐标
    private int mXCenter;
    // 圆心y坐标
    private int mYCenter;
    // 总进度
    private int mTotalProgress = 100;
    // 当前进度
    private int mProgress;
    //下载状态
    private int mStatus = 1;

    //默认显示的图片
    private Bitmap mNotBeginImg;
    //暂停时中间显示的图片
    private Bitmap mPausedImg;
    //等待时显示的图片
    private Bitmap mWatiImg;
    //下载完成时显示的图片
    private Bitmap finishedImg;

    private final int SCALE=2;

    public DownloadPercentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 获取自定义的属性
        initAttrs(context, attrs);
        //初始值设置
        initVariable();
    }

    /** * 初始化自定义属性 * @param context * @param attrs */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.DownloadPercentView, 0, 0);
        mRadius = (int)typeArray.getDimension(R.styleable.DownloadPercentView_radius, 100);
        mStrokeWidth = (int)typeArray.getDimension(R.styleable.DownloadPercentView_strokeWidth, 2);
        mCircleColor = typeArray.getColor(R.styleable.DownloadPercentView_circleColor, 0xFFFFFFFF);
        mRingColor = typeArray.getColor(R.styleable.DownloadPercentView_ringColor, 0xFFFFFFFF);
        mTextColor = typeArray.getColor(R.styleable.DownloadPercentView_textColor, 0xFFFFFFFF);
        mNotBeginImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_notBeginImg)).getBitmap();
        mPausedImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_pausedImg)).getBitmap();
        mWatiImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_waitImg)).getBitmap();
        finishedImg = ((BitmapDrawable)typeArray.getDrawable(R.styleable.DownloadPercentView_finishedImg)).getBitmap();
        mTextSize = (int)typeArray.getDimension(R.styleable.DownloadPercentView_textSize, 28);
        //记住recycle,并避免内存泄漏
        typeArray.recycle();
    }

    private void initVariable() {
        mCirclePaint = new Paint();
        mRingPaint= new Paint();
        mTxtPaint = new Paint();
        //初始化绘制灰色圆的画笔
        initPaint(mCirclePaint,mCircleColor);

        //初始化绘制圆弧的画笔
        initPaint(mRingPaint,mRingColor);

        //初始化绘制文字的画笔
        initTextPaint(mTxtPaint);
        //初始化要显示的图片
        initImage();
    }

    /** * 显示图片的处理 */
    private void initImage() {
        mNotBeginImg = imageSize(mNotBeginImg, mRadius * SCALE, mRadius * SCALE);
        mPausedImg = imageSize(mPausedImg, mRadius * SCALE, mRadius * SCALE);
        mWatiImg = imageSize(mWatiImg, mRadius *SCALE, mRadius * SCALE);
        finishedImg = imageSize(finishedImg, mRadius *SCALE, mRadius * SCALE);
    }

    /** * * 初始化画文字的画笔 * @param mTxtPaint */
    private void initTextPaint(Paint mTxtPaint) {
        mTxtPaint.setAntiAlias(true);
        mTxtPaint.setColor(mTextColor);//Color.parseColor("#52ce90")
        mTxtPaint.setTextAlign(Paint.Align.CENTER);
        mTxtPaint.setTextSize(mTextSize);
    }

    /** * 初始化圈和弧的画笔 * @param targetPaint 区分画笔 * @param color 画笔颜色 */

    private void initPaint(Paint targetPaint,int color) {

        targetPaint.setAntiAlias(true);
        targetPaint.setColor(color);
        targetPaint.setStyle(Paint.Style.STROKE);
        targetPaint.setStrokeWidth(mStrokeWidth);
    }

    /** * 计算控件的宽高 * @param widthMeasureSpec * @param heightMeasureSpec */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = (int)Math.ceil(mRadius) * SCALE;
        setMeasuredDimension(width, width);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //控件的中心坐标
        mXCenter = getWidth() / SCALE;
        mYCenter = getHeight() / SCALE;
        switch (mStatus) {
            case STATUS_NOBEGIN://原始状态
                canvas.drawBitmap(mNotBeginImg, 0, 0, null);
                break;
            case STATUS_WAITING://等待下载状态
                canvas.drawBitmap(mWatiImg, 0, 0, null);
                break;
            case STATUS_DOWNLOADING://下载中
                drawDownloadingView(canvas);
                break;
            case STATUS_PAUSED://暂停
                drawPausedView(canvas);
                break;
            case STATUS_FINISHED://下载完成
                canvas.drawBitmap(finishedImg, 0, 0, null);
                break;
        }

    }

    /** * 绘制下载中的view * @param canvas */
    private void drawDownloadingView(Canvas canvas) {
        //绘制灰色圆环
        canvas.drawCircle(mXCenter, mYCenter, mRadius - mStrokeWidth/SCALE, mCirclePaint);
        //绘制进度扇形圆环
        RectF oval = new RectF();
        //设置椭圆上下左右的坐标
        oval.left = mXCenter - mRadius + mStrokeWidth/SCALE;
        oval.top = mYCenter - mRadius + mStrokeWidth/SCALE;
        oval.right = mXCenter + mRadius - mStrokeWidth/SCALE;
        oval.bottom = mYCenter + mRadius - mStrokeWidth/SCALE;
        canvas.drawArc(oval, -90, ((float)mProgress / mTotalProgress) * 360, false, mRingPaint);
        //绘制中间百分比文字
        String percentTxt = String.valueOf(mProgress);
        //计算文字垂直居中的baseline
        Paint.FontMetricsInt fontMetrics = mTxtPaint.getFontMetricsInt();
        float baseline = oval.top + (oval.bottom - oval.top - fontMetrics.bottom + fontMetrics.top) / SCALE - fontMetrics.top;
        canvas.drawText(percentTxt+"%", mXCenter, baseline, mTxtPaint);
    }

    /** * 绘制暂停时的view * @param canvas */
    private void drawPausedView(Canvas canvas) {
        //绘制灰色圆环
        canvas.drawCircle(mXCenter, mYCenter, mRadius - mStrokeWidth/SCALE, mCirclePaint);

        //绘制进度扇形圆环
        RectF oval = new RectF();
        //设置椭圆上下左右的坐标
        oval.left = mXCenter - mRadius + mStrokeWidth/SCALE;
        oval.top = mYCenter - mRadius + mStrokeWidth/SCALE;
        oval.right = mXCenter + mRadius - mStrokeWidth/SCALE;
        oval.bottom = mYCenter + mRadius - mStrokeWidth/SCALE;
        canvas.drawArc(oval, -90, ((float) mProgress / mTotalProgress) * 360, false, mRingPaint);

        //绘制中间暂停图标
        canvas.drawBitmap(mPausedImg, 0, 0, null);
    }

    /** * 更新进度 * @param progress */
    public void setProgress(int progress) {
        mProgress = progress;
        postInvalidate();//刷新
    }

    /** * 设置下载状态 * @param status */
    public void setStatus(int status) {
        this.mStatus = status;
        postInvalidate();
    }

    /** * 获取下载状态 * @return */
    public int getStatus() {
        return mStatus;
    }

    public static Bitmap imageSize(Bitmap b,float x,float y)
    {
        int w=b.getWidth();//获取图片的宽高
        int h=b.getHeight();
        float sx=x/w;//要强制转换
        float sy=y/h;
        Matrix matrix = new Matrix();
        matrix.postScale(sx, sy); // 长和宽放大缩小的比例
        Bitmap resizeBmp = Bitmap.createBitmap(b, 0, 0, w,
                h, matrix, true);
        return resizeBmp;
    }
}

自定义控件的使用:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
<com.losileeya.downloadpercent_master.DownloadPercentView
        android:id="@+id/downloadView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        custom:notBeginImg="@drawable/ic_no_download"
        custom:waitImg="@drawable/ic_wait"
        custom:pausedImg="@drawable/ic_pause"
        custom:finishedImg="@drawable/ic_finished"
        custom:strokeWidth="4dp"
        custom:circleColor="#bdbdbd"
        custom:radius="40dp"
        custom:ringColor="#52ce90"
        custom:textSize="32sp"
        custom:textColor="#52ce90"
        />
</RelativeLayout>

这里布局的使用就不用说了吧,主要就是申明控件和控件的前缀空间,并且对自定义属性赋值(你喜欢搞什么都可以)。
使用过程贴代码:

package com.losileeya.downloadpercent_master;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import java.lang.ref.WeakReference;
import java.util.Random;

public class MainActivity extends AppCompatActivity {
    public final static int MSG_UPDATE = 1;//更新进度
    public final static int MSG_FINISHED = 2;//完成
    private DownloadPercentView mDownloadPercentView;
    private int mDownloadProgress = 0;//初始进度值
    private Handler mHandler = new ProgressHandler(this);
    private boolean downloading = false;//是否下载中
    private Random mRandom;//模拟进度的随机数

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRandom = new Random();
        mDownloadPercentView = (DownloadPercentView)  findViewById(R.id.downloadView);
        mDownloadPercentView.setOnClickListener(new  View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //当进度为未开始或暂停
                if (mDownloadPercentView.getStatus() == DownloadPercentView.STATUS_NOBEGIN||   
                mDownloadPercentView.getStatus() == DownloadPercentView.STATUS_PAUSED) {
                    //状态下点击变为下载状态
                    downloading = true;
                    mDownloadPercentView      .setStatus(DownloadPercentView.STATUS_DOWNLOADING);
    // 线程控制进度
new Thread(new Runnable() {
    @Override
    public void run() {
         while (downloading) {
      //当进度为100时通过handler把状态转为下载完成,并且退出线程
      if (mDownloadProgress == 100) {
            mHandler.sendEmptyMessage(MSG_FINISHED);
                                    return;
                                }
      //进度随机增加1到10的数
      mDownloadProgress += mRandom.nextInt(10) + 1;
   //模拟过程大于100时显示100,小于则为进度值(真实不会存在大于100)
     mDownloadProgress = mDownloadProgress > 100 ? 100:     
     mDownloadProgress;
                                //handler更新进度
     mHandler.sendEmptyMessage(MSG_UPDATE);
            try {
                    //0.3秒更新一次精度值
                    Thread.sleep(300);
                } catch (Exception e) {
                }}}}).start();
                } else if(mDownloadPercentView.getStatus()   
                == DownloadPercentView.STATUS_DOWNLOADING)  {
                //正在下载时点击则状态改为暂停
                    downloading = false;
                    mDownloadPercentView
            .setStatus(DownloadPercentView.STATUS_PAUSED);
                }
            }
        });
    }

    private class ProgressHandler extends Handler {
        private WeakReference<MainActivity>   
        activityWeakReference;
        public ProgressHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<> 
            (activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                switch (msg.what) {
                case MSG_FINISHED:
                    mDownloadPercentView
        .setStatus(DownloadPercentView.STATUS_FINISHED);
                    break;
                case MSG_UPDATE:
         mDownloadPercentView.setProgress(mDownloadProgress);
                    break;
                }
                super.handleMessage(msg);
            }
        }
    }
}

这里没有网络请求,所以为了模仿真一点,进度每次变化用的是随机数,哈哈,就写到这里了,觉得有用帮忙顶起来。

由于没有传图片上来,想看一下效果的
demo 传送门:DownloadPercent-master.rar

你可能感兴趣的:(app下载进度)