【Android自定义view系列】圆形百分比进度条

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xygy8860/article/details/55101326

1.前言

       本文由xygy8860原创,首发地址:http://blog.csdn.net/xygy8860/article/details/55101326,转载请注明。

       在工作工程中,自己掌握的Android开发知识已经感觉到了瓶颈,Android初级知识没问题,写业务逻辑也没问题。但是作为一个Android开发工程师,并不能仅仅满足于现状,看到自身缺陷和瓶颈之后,需要对自身进行努力提高,以提升自身技能。

       对于Android开发而言,自定义控件一直是Android开发进阶的一大方向,更何况,如今的APP已经不是简单的功能实现,用户对于APP的体验已经从功能实现转为用户体验,画面是否精美,转场动画是否别出心裁,UI是否美观大方?等等这些内容是必须考虑的。现如今同类APP越来越多,公司为了赢得客户,提高留存率,必然在体验和UI上下功夫。如果这时设计师设计出精美的UI,开发工程师说:I can't !,那么估计老板会说go out 了!

       基于以上,开始了自定义View的学习。同时在学习过程中,记录下来,如果你也对自定义View有兴趣,我们也可以一同成长!

2.圆形百分比进度条UI分析

       2.1为何会选择圆形百分比进度条?

       现在下载进度条等已经高度自定义,传统的progressbar已经样式落后。在使用360手机助手等的过程中,圆形百分比进度条已经普遍而且非常精美,是常规下载组件的一部分。因此,圆形百分比进度条作为第一个学习对象。同时,在学习个过程中,完善一个自定义view的框架,可以很方便的集成各种自定义View。

       在本文的学习过程中,学习参考了这篇文章http://blog.csdn.net/wingichoy/article/details/50334595,对于博主后面的自定义View文章,也准备系统的跟着学习下。在此感谢博主的文章,指出了自定义View学习的道路。

       2.2 废话不说,上效果

      

       录制的gif有点卡顿,不过在真机上还是很流畅的。

      2.3 自定义view知识储备

       我们都知道,在自定义view的时候一般都要重写三个方法:onMeasure(),onLayout()和onDraw()。

onDraw()必须有,用来绘制View图像
如果要改变View大小,需要重写onMeasure()
如果要改变View在父控件中的位置,需要重写onLayout()
       view还有其他的方法,也一并了解下,对于自定义view有时候非常有用。

>> onFinishInflate(): 这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构建界面之后,该方法将会被回调。

>> onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小。

>> onLayout(boolean,int,int,int,int):当该组件需要分配其自组件的位置、大小时,该方法就会被回调。

>> onSizeChanged(int,int,int,int):当该组件的大小被改变时回调该方法。

>> onDraw(Canvas):当该组件需要绘制它的内容时回调该方法进行绘制。

>> onKeyDown(int,KeyEvent):当某个键被按下时触发该方法。

>> onKeyUp(int,KeyEvent):当松开某个按键时触发该方法。

>> onTrackballEvent(MotionEvent):当发生轨迹球事件时触发该方法

>> onTouchEvent(MotionEvent):当发生触摸屏事件时触发该方法

>> onWindowFocusChanged(boolean):当该组件得到、失去焦点时触发该方法。

>> onAttachedToWindow():当把组件放入某个窗口时触发该方法

>> onDetachedFrowWindow():当把组件从某个窗口上分离时触发该方法。

>> onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该方法。

      2.4 UI分析

       在看到上面这个UI的时候,首先我们需要分析下UI的组成部分。任何高级UI都是有初级UI元素组成的,把一个高级UI拆解成一个个初级UI的组成部分,那么也就离我们实现它不远了。

       从上面的gif图上我们可以看到,自定义圆形百分比进度条,含有三个元素:(1)一个大圆;(2)一个弧形色带;(3):百分比进度的文字。从上面的拆解我们可以发现,大圆好实现,文字也好实现,但是弧形色带怎么实现呢?

       其实,经过分析我们也可以再次分解,将色带分解为:(1)一个圆弧;(2)一个中心小圆。这样实现是不是更加容易了呢?

       最终,我们将UI拆解为四个部分:

(1)外层大圆;
(2)中间圆弧;
(3)内层小圆;
(4)中心文字。
       其图层显示如下(图片来源于参考博客,感谢博主):

      这里写图片描述这里写图片描述这里写图片描述

3.圆形百分比进度条代码编写

       3.1 继承View并实现构造函数

       自定义view的第一步,必然是继承View或viewgroup,由于我们此文主要实现是控件,而不需要布局,同时我们是完全自绘控件,所以只需要继承View即可,同时实现构造函数。首先我们定义一个CircleProgressBar的自定义类,并实现构造函数。代码如下:

    // 将前面二个构造函数都指向第三个
    public CircleProgressBar(Context context) {
        this(context, null);
    }

    // XML中使用此构造函数
    public CircleProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 默认值,色带宽度,文字大小等
        mStripeWidth = PxUtils.dpToPx(30, context);
        mCenterTextSize = PxUtils.spToPx(20, context);
        mRadius = PxUtils.dpToPx(100, context);

        //绘制大圆画笔属性设置
        bigCirclePaint = new Paint();
        bigCirclePaint.setAntiAlias(true); // 抗锯齿
        bigCirclePaint.setColor(mCircleColor); // 设置大圆颜色

        //绘制小圆画笔属性设置
        smallCirclePaint = new Paint();
        smallCirclePaint.setAntiAlias(true);
        smallCirclePaint.setColor(mCircleColor);

        // 饼状图属性
        rect = new RectF();
        sectorPaint = new Paint();
        sectorPaint.setColor(mAngleColor);
        sectorPaint.setAntiAlias(true);

        // 文字画笔属性
        textPaint = new Paint();
        textPaint.setColor(Color.WHITE);
    }

       3.2 onMeasure

       由于我们不涉及到改变控件在viewparent的位置问题,所以不需要重写onLayout()。所以我们重写的第一步是onMeasure()。

       在重写onMeasure()的时候,需要了解一些其他知识。我们都知道,在xml中我们需要设置layout_height和layout_width属性,那么,控件在onMeasure()的时候,就需要用到这些属性。根据这篇博客http://blog.csdn.net/lmj623565791/article/details/24252901的解读:

当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
MeasureSpec对象包含了测量的模式和大小。他是一个32位的int值,其中高两位为测量的模式,低30位是测量的大小。采用位运算和运行效率有关。所以可以从一个MeasureSpec对象分别获取模式和值 如:

//获取模式  值为 EXACTLY AT_MOST UNSPECIFIED
int specMode = MeasureSpec.getMode(measureSpec);
//获取测量值
int specSize = MeasureSpec.getSize(measureSpec);

       了解了测量模式之后,我们就可以根据specMode 的值,来判断当前layout_height和layout_width的属性,从而计算出当前控件的实际宽高,此文中也即获得了大圆的半径。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //获取测量大小
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // match_parent
        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
            mRadius = widthSize / 2;
            x = widthSize / 2;
            y = heightSize / 2;
            mWidth = widthSize;
            mHeight = heightSize;
        }

        // warp_content
        // 如果是自适应,则取默认值
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            mWidth = (int) (mRadius * 2);
            mHeight = (int) (mRadius * 2);
            x = mRadius;
            y = mRadius;
        }
        setMeasuredDimension(mWidth, mHeight);
    }

       3.3 onDraw

       在控件测量完成之后,系统会调用onDraw()方法绘制控件,在此重写,达到绘制圆形百分比进度条的目的。onDraw()主要分四个方面,即上文提到的大圆、圆弧、小圆和文字,逐层进行绘制,顺序不可调换,不然会导致覆盖。

    @Override
    protected void onDraw(Canvas canvas) {

        mEndAngle = (int) (mCurPercent * 3.6);

        // 大圆
        canvas.drawCircle(x, y, mRadius, bigCirclePaint); // 通过canvas画圆

        //饼状图
        rect.right = mWidth;
        rect.bottom = mHeight;
        //参数说明见知识补充
        canvas.drawArc(rect, 270, mEndAngle, true, sectorPaint);

        // 小圆
        canvas.drawCircle(x, y, mRadius - mStripeWidth, smallCirclePaint);

        //绘制文本
        String text = mCurPercent + "%";
        textPaint.setTextSize(mCenterTextSize);
        float textLength = textPaint.measureText(text);
        canvas.drawText(text, x - textLength / 2, y, textPaint);
    }

       3.4 外部调用

       布局方面写完之后,我们还需要向外界暴露一个借口,以控制进度条的显示和文字的改变。

    // 外部设置百分比数
    public void setPercent(int percent) {
        if (percent > 100 || percent < 0) {
            throw new IllegalArgumentException("percent must more than 0 and less than 100!");
        }
        mCurPercent = percent;
        invalidate();
    }

       3.5 成果

       至此大功告成,我们只需要在布局中引用,即可验证我们的成果。布局中代码也很简单,和普通控件一样即可。

<com.chenghui.customview.widget.CircleProgressBar
        android:id="@+id/circleView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

       然后在代码中findViewById即可得到实例,根据下载进度,持续调用setPercent()方法就能达到动画效果。

      

       3.6 扩展

       在代码写完,功能实现之后,为了更好的扩展性,必须对控件的属性进行接口暴露,使用者能根据自己的意愿对控件进行适当的修改,以和应用的整体风格契合。因此,暴露以下方法:

    // 设置圆的颜色
    public void setCircleColor(int mCircleColor) {
        this.mCircleColor = mCircleColor;
        smallCirclePaint.setColor(mCircleColor);
        bigCirclePaint.setColor(mCircleColor);
        // 此方法是为了使设置生效
        invalidate();
    }

    // 设置圆弧的颜色
    public void setAngleColor(int mAngleColor) {
        this.mAngleColor = mAngleColor;
        sectorPaint.setColor(mAngleColor);
        invalidate();
    }

    // 设置色带宽度
    public void setStripeWidth(float mStripeWidth) {
        this.mStripeWidth = mStripeWidth;
        invalidate();
    }

    // 设置字体颜色
    public void setCenterTextColor(int color) {
        textPaint.setColor(color);
        invalidate();
    }

    // 设置字体大小
    public void setmCenterTextSize(float mCenterTextSize) {
        this.mCenterTextSize = mCenterTextSize;
        invalidate();
    }

      

4.源码

       本文的源码已经上传github,地址:https://github.com/xygy8860/CustomView求star),请各位自取,下载源码后可以按自己的风格自行修改



猜你喜欢

转载自blog.csdn.net/xygy8860/article/details/55101326