当前位置: 移动技术网 > IT编程>移动开发>Android > Android 自定义view之圆盘进度条

Android 自定义view之圆盘进度条

2017年12月28日  | 移动技术网IT编程  | 我要评论

三宿山,非走不可,养正西山学校

很久没有用到自定义View了,手有点生疏了,这不同事刚扔给一个活,按照UI的要求,画一个进度条,带动画效果的。需求是这样的:这里写图片描述
嗯,实现后效果如下:

这里写图片描述

嗯,算是基本满足需求吧。
本文包含的知识点
1、自定义view的绘制
2、属性动画
3、图像的合成模式 PorterDuff.Mode

嗯,废话不多说,show me the code
1)WordView.java

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Author: gongwq on 2017/12/20 002011.
 * Email: gwq_0111@163.com
 * Descriptions:
 */

public class WordView extends View {
    private Paint linePaint, circlePaint, circlePaintBg;
    private Paint textPaint1, textPaint2, textPaint3, textPaint4;
    private int screenWidth, screenHeight;
    private int mCenter, mRadius;
    private RectF mRectF, mRectFBg1, mRectFBg2;
    private int defaultValue;
    private float rate = 0f;
    private String hasStudyText = "";
    private String notStudyText = "";
    private int startAng = 0;
    private int defaultStartAng = 120;
    private long animDuration = 2000L;
    private int progressColor, wordTitleColor, wordViewBackground;
    private Xfermode xfermode;

    public WordView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        TypedArray ta = context.obtainStyledAttributes(
                attrs, R.styleable.WordView);

        progressColor = ta.getColor(R.styleable.WordView_progressColor, 0xFFFFFFFF);
        wordViewBackground = ta.getColor(R.styleable.WordView_wordViewBackground, 0xFFFF0000);
        wordTitleColor = ta.getColor(R.styleable.WordView_wordTitleColor, 0xFFFF00FF);
        ta.recycle();
        initPaint(context);
    }

    private void initPaint(Context context) {
        screenWidth = MeasureUtil.getScreenWidth(context);
        screenHeight = MeasureUtil.getScreenHeight(context);
        linePaint = new Paint();
        linePaint.setColor(Color.WHITE);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setAntiAlias(true);
        linePaint.setStrokeWidth(6.0f);

        circlePaint = new Paint();
        circlePaint.setColor(progressColor);
        circlePaint.setAntiAlias(true);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeCap(Paint.Cap.ROUND);
        circlePaint.setStrokeWidth(6.0f);

        circlePaintBg = new Paint();
        circlePaintBg.setColor(wordViewBackground);
        circlePaintBg.setAntiAlias(true);
        circlePaintBg.setStyle(Paint.Style.FILL);

        textPaint1 = new Paint();//已学习生词
        textPaint1.setColor(wordTitleColor);
        textPaint1.setTextAlign(Paint.Align.CENTER);
        textPaint1.setAntiAlias(true);
        textPaint1.setTextSize(25);

        textPaint2 = new Paint();//xx个
        textPaint2.setColor(Color.WHITE);
        textPaint2.setTextAlign(Paint.Align.CENTER);
        textPaint2.setAntiAlias(true);
        textPaint2.setTextSize(70);

        textPaint4 = new Paint();//个
        textPaint4.setColor(Color.WHITE);
        textPaint4.setTextAlign(Paint.Align.CENTER);
        textPaint4.setAntiAlias(true);
        textPaint4.setTextSize(25);

        textPaint3 = new Paint();//未学习生词x个
        textPaint3.setColor(wordTitleColor);
        textPaint3.setTextAlign(Paint.Align.CENTER);
        textPaint3.setAntiAlias(true);
        textPaint3.setTextSize(25);

        mCenter = screenWidth / 2;
        mRadius = screenWidth / 4;

        mRectF = new RectF(10, 10, 2 * mRadius - 10, 2 * mRadius - 10);//外切圆弧

        mRectFBg1 = new RectF(20, 20, 2 * mRadius - 20, 2 * mRadius - 20);//中间的背景

        mRectFBg2 = new RectF(60, 2 * mRadius - 60, 2 * mRadius - 60, 2 * mRadius + 60);//通过这个去调下面“缺”的弧度
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);//DST_OUT在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawArc(mRectF, startAng, (360 - (startAng - 90) * 2) * rate, false, circlePaint);
        //将绘制操作保存到新的图层,将图像合成的处理放到离屏缓存中进行
        circlePaintBg.setColor(wordViewBackground);
        int saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
        canvas.drawArc(mRectFBg1, 0, 360, true, circlePaintBg);//绘制目标图
        circlePaintBg.setXfermode(xfermode);//设置混合模式

        circlePaintBg.setColor(Color.parseColor("#FF00FF00"));//这里的颜色只需前面的透明值为FF即完全不透明即可
        canvas.drawArc(mRectFBg2, 0, 360, true, circlePaintBg);//绘制源图
        circlePaintBg.setXfermode(null);//清除混合模式
        canvas.restoreToCount(saveCount);

        canvas.drawText("已学习生词", mRadius, mRectF.top + 100, textPaint1);
        float width = textPaint2.measureText(hasStudyText);
        float width2 = textPaint4.measureText("个");
        //   float total = mRectFBg1.right - mRectFBg1.left;
        //  Log.e("文字宽度---->", width + "  " + width2 + "  " + total);
        // canvas.drawText(hasStudyText, mRadius + 30, mRadius, textPaint2);
        float center1 = mRadius - (width + width2) / 2 + width / 2;
        float center2 = mRadius - (width + width2) / 2 + width + width2 / 2;
        canvas.drawText(hasStudyText, center1, mRadius + 20, textPaint2);

        canvas.drawText(notStudyText, mRadius, mRectF.bottom - 100, textPaint3);
        if (startAng != 0) {
            canvas.drawText("个", center2, mRadius + 20, textPaint4);
            canvas.rotate(180 + (startAng - 90) + (360 - (startAng - 90) * 2) * rate, mRadius, mRadius);
            if (Integer.parseInt(hasStudyText) > 0) {
                canvas.drawLine(mRadius, mRectF.top, mRadius, mRectF.top + 20, linePaint);
            }
        }
    }

    /**
     * 设置学习单词数
     *
     * @param hasStudyNum 已学习单词数
     * @param notStudyNum 未学习单词数
     */
    public void setWordsNum(final int hasStudyNum, final int notStudyNum) {
        rate = (float) hasStudyNum / (float) (hasStudyNum + notStudyNum);
        final float rate2 = rate;

        startAng = getStartAng();//startAng必须为大于等于90,小于180
        ValueAnimator anim = ValueAnimator.ofFloat(1, 100);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                rate = (float) animation.getAnimatedValue() * rate2 / 100f;
                hasStudyText = (int) ((float) animation.getAnimatedValue() * hasStudyNum / 100) + "";
                notStudyText = "未学习生词" + (int) ((float) animation.getAnimatedValue() * notStudyNum / 100) + "个";
                postInvalidate();
            }
        });
        //  hasStudyText = hasStudyNum + "";
        //  notStudyText = "未学习生词" + notStudyNum+"个";
        anim.setDuration(getAnimationDuration());
        anim.start();

    }

    /**
     * 设置圆弧的起始角度值  
注 1.值必须是[90,180] 2.必须在setWordNum()方法之前调用
     * @param ang
     */
    public void setStartAng(int ang) {
        this.startAng = ang;
    }

    public int getStartAng() {
        if (startAng == 0) {
            return defaultStartAng;
        }
        return startAng;
    }

    /**
     * 设置动画时间 ,注意 需要在setWordsNum前调用才会生效
     *
     * @param time
     */
    public void setAnimationDuration(long time) {
        this.animDuration = time;
    }

    public long getAnimationDuration(){
        return animDuration;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

    private int measureWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        // 默认宽高;
        defaultValue = screenWidth;

        switch (mode) {
            case MeasureSpec.AT_MOST:
                // 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
               // Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);
                //  size = Math.min(defaultValue, size);
                size = screenWidth / 2;
                defaultValue = size;
                break;
            case MeasureSpec.EXACTLY:
                // 精确值模式
                // 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时

                break;
            default:
                size = defaultValue;
                break;
        }

        return size;
    }

    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        switch (mode) {
            case MeasureSpec.AT_MOST:
                // 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
                Log.e("cmos---->", "size " + size + " screenHeight " + screenHeight);
                //   size = Math.min(screenHeight / 2, size);
                size = defaultValue;
                break;
            case MeasureSpec.EXACTLY:
                // 精确值模式
                // 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时

                break;
            default:
                size = defaultValue;
                break;
        }
        return size;
    }
}

下面是他的一些配置
attrs.xml



    
        
        
        
    

布局文件
activity_main.xml




    

    

    

使用
MainActivity.java

 ......
 @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt:
                if (!TextUtils.isEmpty(et1.getText().toString()) && !TextUtils.isEmpty(et2.getText().toString())) {
                    int num1 = Integer.parseInt(et1.getText().toString());
                    int num2 = Integer.parseInt(et2.getText().toString());
                    wordView.setAnimationDuration(4000);
                    wordView.setStartAng(130);
                    wordView.setWordsNum(num1, num2);
                } else {
                    Toast.makeText(this, "数值不能为空", Toast.LENGTH_SHORT).show();
                }

                break;
        }
    }
    ......

代码里注释已经相对较清晰了,就不做解释了,有不懂的可以留言。
整个view的重点就在onDraw()方法里,怎么去放置文字,中间的“xxx个”怎么随着数字的长度变化而始终居中,这主要与initPaint()画笔方法有关,其中textPaint.setTextAlign(Paint.Align.CENTER);是重点,它表示画的文字,你后面给定他一个绘制的中心点,然后它的文字会自动居中。第二个要注意的地方是,中间的背景,我是画的,开始准备用UI给的背景的,但是发现不好适配,所以就自己画了,这里主要用到的是图像合成模式PorterDuff.Mode,图像的合成模式的枚举类一共有16种,通过这16种模式,我们可以自己根据给定的2个图片,合成我们想要的结果。这也给我们一个启示:当需求中的图片可以看成是多个图片组合成的结果的画,不妨可以试试 图像的合成模式PorterDuff.Mode。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网