Android自定义View:进度条+冒泡文本

简介

最近看到有这样的需求:显示进度条,描述文本显示在进度条的刻度上面。正好练练手,回顾下自定义View知识。

分析

通过上图,我们可以看到,该UI显示了文本,而文本显示在一张图片中,有一个默认的进度条和根据实际进度显示的进度条。我们可以将其拆分成4个组成部分:

(1)图片,作为文本的背景图,这个背景图应该伸缩不失真,建议用.9图;

(2)文本,有颜色和大小,显示在图片上,居中显示;

(3)默认进度条,有颜色和宽高;

(4)真正的进度条:有颜色和进度值、宽高。它比较特殊的是当进度不等于100%时,左边是圆弧,右边是直线型(没有圆角)。

代码

首先在构造函数初始化画笔,在onSizeChanged中确定控件的宽高,设置画笔的颜色,然后在onDraw中开始绘制内容

private Context mContext;
    /**
     * 背景图画笔
     */
    private Paint mBackgroundPaint;
    /**
     * 进度画笔
     */
    private Paint mProgressPaint;
    /**
     * 文本画笔
     */
    private Paint mTextPaint;
    /**
     * 图片画笔
     */
    private Paint mPicturePaint;
    /**
     * 背景矩形颜色
     */
    private int mBackgroundColor = Color.BLACK;
    /**
     * 进度矩形颜色
     */
    private int mProgressColor = Color.GRAY;
    /**
     * 文本颜色
     */
    private int mTextColor = Color.BLACK;
    /**
     * 文本大小
     */
    private int mTextSize = 28;
    /**
     * 控件宽高:控件必须在布局中指定宽高大小
     */
    private int mWidth;
    private int mHeight;
    /**
     * 进度矩形高度
     */
    private int mProgressHeight = 10;
    /**
     * 图片宽高
     */
    private int mPicWidth;
    private int mPicHeight;
    /**
     * 矩形4个角的半径坐标,左上,右上,右下,左下(顺时针)
     */
    private float[] mRadiusArr = new float[]{0f,0f,0f,0f,0f,0f,0f,0f};
    /**
     * 进度
     */
    private float mProgress;
    /**
     * 文本
     */
    private String mText;

    public ProgressBubbleView(Context context) {
        super(context);
        initView(context);
    }

    public ProgressBubbleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public ProgressBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context){
        mContext = context;

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setStyle(Paint.Style.FILL);
        mBackgroundPaint.setStrokeWidth(1);

        mBackgroundPaint.setAntiAlias(true);

        mProgressPaint = new Paint();
        mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mProgressPaint.setStrokeWidth(2);
        mProgressPaint.setAntiAlias(true);

        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setAntiAlias(true);

        mPicturePaint = new Paint();
        mPicturePaint.setStyle(Paint.Style.STROKE);
        mPicturePaint.setAntiAlias(true);
        setLayerType(LAYER_TYPE_SOFTWARE,null);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;

        mBackgroundPaint.setColor(mBackgroundColor);
        mProgressPaint.setColor(mProgressColor);
        mTextPaint.setColor(mTextColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘制文本背景框
        drawPicture(canvas);
        //绘制文本
        drawText(canvas);
        //将原点坐标移到文本之下
        canvas.translate(0,mPicHeight-28);//因为图片底部有留白,导致图片与进度条的距离看起来偏大,所以减去一段距离
        //绘制背景圆角矩形
        drawBackgroud(canvas);
        //绘制进度矩形圆角
        drawProgress(canvas);
    }

绘制图片

    /**
     * 绘制文本背景框
     * @param canvas
     */
    private void drawPicture(Canvas canvas){
        if(this.mProgress == 0)
            return;
        float percent = this.mProgress / 100 * 1.0f;
        int width = (int) (percent * mWidth);
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.infowindow_bg);
        mPicWidth = bitmap.getHeight();
        mPicHeight = bitmap.getWidth();
        int pos = width - mPicWidth/2 - 13;
        canvas.drawBitmap(bitmap,pos,0,mPicturePaint);

    }

绘制文本

    /**
     * 绘制文本
     * @param canvas
     */
    private void drawText(Canvas canvas){
        if(this.mProgress == 0)
            return;
        float percent = this.mProgress / 100 * 1.0f;
        int width = (int) (percent * mWidth);
        float length = mTextPaint.measureText(mText);
        canvas.drawText(mText,width-length/2,mPicHeight/3,mTextPaint);
    }

绘制默认进度条

    /**
     * 绘制背景矩形圆角
     * @param canvas
     */
    private void drawBackgroud(Canvas canvas){
        RectF rectF = new RectF();
        rectF.left = 0;
        rectF.top = 0;
        rectF.right = mWidth;
        rectF.bottom = mProgressHeight;

        allRoundRadius();
        Path path = new Path();
        path.addRoundRect(rectF, mRadiusArr,Path.Direction.CW);
        canvas.drawPath(path,mBackgroundPaint);
    }

这里利用path类的addRoundRect方法的第2个参数float[] radii来实现矩形四个角是圆角还是直角效果。radii是一个大小为8的数组,2个元素为1对,表示矩形的一个角,分别为左上,右上,右下,左下(顺时针)。

绘制进度条

    /**
     * 绘制进度矩形圆角(只有左上和左下是圆角)
     * @param canvas
     */
    private void drawProgress(Canvas canvas){
        if(this.mProgress == 0)
            return;
        float percent = this.mProgress / 100 * 1.0f;
        int width = (int) (percent * mWidth);

        RectF rectF = new RectF();
        rectF.left = 0;
        rectF.top = 0;
        rectF.right = width;
        rectF.bottom = mProgressHeight;

        if (this.mProgress < 100){
            mRadiusArr[0] = 20;
            mRadiusArr[1] = 20;
            mRadiusArr[2] = 0;
            mRadiusArr[3] = 0;
            mRadiusArr[4] = 0;
            mRadiusArr[5] = 0;
            mRadiusArr[6] = 20;
            mRadiusArr[7] = 20;
        }else{
            allRoundRadius();
        }
        Path path = new Path();
        path.addRoundRect(rectF,mRadiusArr,Path.Direction.CW);
        canvas.drawPath(path,mProgressPaint);
    }

    /**
     * 4个角都是圆角
     */
    private void allRoundRadius(){
        mRadiusArr[0] = 20;
        mRadiusArr[1] = 20;
        mRadiusArr[2] = 20;
        mRadiusArr[3] = 20;
        mRadiusArr[4] = 20;
        mRadiusArr[5] = 20;
        mRadiusArr[6] = 20;
        mRadiusArr[7] = 20;
    }

在drawProgress中,当进度不等于100%时,左边是圆弧,右边是直线型,所以设置左上、左下的圆角半径为20,其他2个角半径为0,从而实现左边圆弧,右边直线的圆角矩形效果。

开放的接口

    /**
     * 设置是否矩形4个角都是圆角
     * @param isAllRound
     */
    public void setFourRoundRect(boolean isAllRound){
        if (isAllRound){
            allRoundRadius();
        }
        invalidate();
    }

    /**
     * 设置进度值0-100
     * @param progress
     */
    public void setProgress(float progress){
        this.mProgress = progress;
        invalidate();
    }

    /**
     * 设置文本
     * @param text
     */
    public void setText(String text){
        this.mText = text;
        invalidate();
    }

    public void  setBackgroundColor(int backgroundColor){
        this.mBackgroundColor = backgroundColor;
        invalidate();
    }

    public void  setProgressColor(int progressColor){
        this.mProgressColor = progressColor;
        invalidate();
    }

    public void  setTextColor(int textColor){
        this.mTextColor = textColor;
        invalidate();
    }

效果图

最后

当然,这只是粗略的实现,还是有些问题遗留的。比如进度为0或100时,文本和文本背景图如何显示;进度显示是静态的,如何动态实现显示呢等等。我们可以根据具体的需求去控制,我就不一一描述了。

posted @ 2017-08-18 16:34  ha_cjy  阅读(1434)  评论(0编辑  收藏  举报