自定义view自定义控件Android开发

Android自定义View-NumberProgress

2016-03-23  本文已影响641人  晓峰残月

前言

个人网站已经上线运行,欢迎各位收藏:http://jwenfeng.com
在 android 应用的开发中 ProgressBar 是一个必不可少的控件之一,一般有两种形态,一种事圆形的 ProgressBar,它主要在加载图片,加载更多,刷新等场景中使用,另一种就是横向的 ProgressBar,它主要用来显示下载的进度等等。
下面我自定义的这个 NumberProgress 和系统的 ProgressBar 没有什么关系,是继承View自定义的一个控件,看下图:

效果图

分析

NumberProgress的构成如下图,主要有3块,左边 Reached 部分,中间的文字部分,以及右边的 UnReached 部分。

实现

1. 首先定义属性

在value文件夹中的attrs.xml中添加以下属性

<declare-styleable name="NumberProgress">
        <!-- 文字大小 -->
        <attr name="textSize" format="dimension"/>
        <!-- 文字颜色 -->
        <attr name="textColor" format="color"/>
        <!-- reached的高度 -->
        <attr name="reachedHeight" format="dimension"/>
        <!-- reached的颜色 -->
        <attr name="reachedColor" format="color"/>
        <!-- unReached的高度 -->
        <attr name="unReachedHeight" format="dimension"/>
        <!-- unReached的颜色 -->
        <attr name="unReachedColor" format="color"/>
        <!-- 当前进度 -->
        <attr name="currentProgress" format="integer"/>
        <!-- 总进度 -->
        <attr name="maxProgress" format="integer"/>
    </declare-styleable>
2. 在 NumberProgress 中获取定义属性
    /**
     * 百分比文字颜色
     * */
    private int textColor;
    /**
     * 百分比文字大小
     * */
    private float textSize;
    /**
     * reached部分的颜色
     * */
    private int reachedColor;
    /**
     * reached部分的高度
     * */
    private float reachedHeight;
    /**
     * unReached部分的颜色
     * */
    private int unReachedColor;
    /**
     * unReached部分的高度
     * */
    private float unReachedHeight;
    /**
     * 当前进度
     * */
    private int currentProgress;
    /**
     * 总进度
     * */
    private int maxProgress;
public NumberProgress(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.NumberProgress);
        textColor  = a.getColor(R.styleable.NumberProgress_textColor, Color.parseColor("#3498DB"));
        textSize = sp2px(context, a.getDimension(R.styleable.NumberProgress_textSize, 14f));
        reachedHeight = dip2px(context, a.getDimension(R.styleable.NumberProgress_reachedHeight, 1.25f));
        reachedColor = a.getColor(R.styleable.NumberProgress_reachedColor, Color.parseColor("#3498DB"));
        unReachedHeight = dip2px(context, a.getDimension(R.styleable.NumberProgress_unReachedHeight, 0.75f));
        unReachedColor = a.getColor(R.styleable.NumberProgress_unReachedColor, Color.parseColor("#CCCCCC"));

        currentProgress = a.getInt(R.styleable.NumberProgress_currentProgress, 0);
        maxProgress = a.getInt(R.styleable.NumberProgress_maxProgress, 100);

        reachedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        reachedPaint.setColor(reachedColor);

        unReachedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        unReachedPaint.setColor(unReachedColor);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);

        a.recycle();
    }
3. 计算控件的大小

一个MeasureSpec由大小和模式组成。
它有三种模式:

  1. UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
  2. EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
  3. AT_MOST(至多),子元素至多达到指定大小的值。
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
         * 一个MeasureSpec由大小和模式组成。
         * 它有三种模式:
         * UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
         * EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
         * AT_MOST(至多),子元素至多达到指定大小的值。
         * */
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

    private int measureHeight(int heightMeasureSpec) {
        int result;
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int padding = getPaddingTop()+getPaddingBottom();
        if (mode == MeasureSpec.EXACTLY){
            result = size;
        }else{
            result = getSuggestedMinimumHeight();
            result += padding;
            if (mode == MeasureSpec.AT_MOST){
                result = Math.min(result, size);
            }
        }
        return result;
    }

    private int measureWidth(int widthMeasureSpec) {
        int result;
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int padding = getPaddingLeft()+getPaddingRight();
        if (mode == MeasureSpec.EXACTLY){
            result = size;
        }else{
            result = getSuggestedMinimumWidth();
            result += padding;
            if (mode == MeasureSpec.AT_MOST){
                result = Math.max(result, size);
            }
        }
        return result;
    }

    @Override
    protected int getSuggestedMinimumWidth() {
        return (int) textSize;
    }

    @Override
    protected int getSuggestedMinimumHeight() {
        return Math.max((int) textSize, Math.max((int) reachedHeight, (int) unReachedHeight));
    }
4. 计算 reached、unReached 和文字的大小以及位置

在计算中有3中情况:

  1. 进度为0的时候。当进度为0的时候reached是不用绘制的,需要绘制文字和unReached部分
  2. 进度不为0的时候。当进度不为0的时候,3个部分都需要绘制。
  3. 当reached部分和文字部分大于等于控件的宽度的时候。这个时候 unReached 部分不需要绘制,且reached的右边的位置为整个宽度减去文字宽度。
@Override
    protected void onDraw(Canvas canvas) {
        calculateDrawRectF();
        if (drawReachedBar) {
            canvas.drawRect(reachedRectF, reachedPaint);
        }

        canvas.drawText(currentDrawText, drawTextStart, drawTextEnd, textPaint);

        if (dawUnreachedBar) {
            canvas.drawRect(unReachedRectF, unReachedPaint);
        }
    }

    private void calculateDrawRectF() {
        currentDrawText = String.format("%d", currentProgress * 100 / maxProgress);
        currentDrawText = currentDrawText+"%";
        float drawTextWidth = textPaint.measureText(currentDrawText);

        if (currentProgress == 0){
            drawReachedBar = false;
            drawTextStart = getPaddingLeft();
            reachedRectF.right = 0;
        }else{
            drawReachedBar = true;
            reachedRectF.left = getPaddingLeft();
            reachedRectF.top = getHeight()/2.0f-reachedHeight/2.0f;
            reachedRectF.right = (getWidth()-getPaddingLeft()-getPaddingRight())/(maxProgress*1.0f) * (float)currentProgress;
            reachedRectF.bottom = getHeight()/2.0f+reachedHeight/2.0f;
            drawTextStart = reachedRectF.right;
        }

        if ((drawTextStart+drawTextWidth) >= (getWidth()-getPaddingRight())){
            drawTextStart = getWidth() - getPaddingRight() - drawTextWidth;
            reachedRectF.right = drawTextStart;
        }

        drawTextEnd =  (int) ((getHeight() / 2.0f) - ((textPaint.descent() + textPaint.ascent()) / 2.0f));
        float unreachedBarStart = reachedRectF.right+drawTextWidth;
        if (unreachedBarStart >= getWidth() - getPaddingRight()) {
            dawUnreachedBar = false;
        }else{
            dawUnreachedBar = true;
            unReachedRectF.left = unreachedBarStart;
            unReachedRectF.right = getWidth() - getPaddingRight();
            unReachedRectF.top = getHeight() / 2.0f + -unReachedHeight / 2.0f;
            unReachedRectF.bottom = getHeight() / 2.0f + unReachedHeight / 2.0f;
        }
    }
5. 测试

测试就没什么说的了

numberProgress = (NumberProgress) findViewById(R.id.number_progress);
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                current++;
                if (current > 100){
                    current = 0;
                }
                numberProgress.setCurrentProgress(current);
                handler.postDelayed(this,80);
            }
        },80);

效果图:


效果图

完整代码请移步github:https://github.com/823546371/NumberProgress
本文地址:http://www.jianshu.com/p/755a6bd6a6cc
尊重原创,转载请注明:From 晓峰残月(http://jwenfeng.com) 侵权必究!

上一篇 下一篇

猜你喜欢

热点阅读