当前位置:   article > 正文

Android知识梳理之自定义View_android studio 的postinvalidate

android studio 的postinvalidate

       虽然android本身给我们提供了形形色色的控件,基本能够满足日常开发的需求,但是面对日益同质化的app界面,和不同的业务需求。我们可能就需要自定义一些View来获得比较好的效果,自定义View是android开发者走向高级开发工程师必须要走的一关。自定义View主要包含三块自定义Viewgroup自绘View组合View。本文我们主要讲自绘View。

                                                                                             

一.View的构造函数:

当我们创建一个类去继承View的时候,系统会要求我们至少去实现一个构造函数。

         

  • public MyView(Context context)    该构造函数是直接在代码里面进行创建控件的时候调用.如我们创建一个MyView myView=new MyView(this)的时候将会调用该函数。
  • public MyView(Context context, AttributeSet attrs) 个是在xml创建但是没有指定style的时候被调用.多了一个AttributeSet类型的参数,在通过布局文件xml创建一个view时,会把XML内的参数通过AttributeSet带入到View内。
  •  public MyView(Context context, AttributeSet attrs, int defStyleAttr) 构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会生效。
  •  public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 该构造函数是在api21的时候才添加上的,暂不考虑。

注意:即使你在View中使用了Style这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。

一般我们在自定义View时候构造函数可以写成这样:

  1. public MyView(Context context) {
  2. this(context, null);
  3. }
  4. public MyView(Context context, AttributeSet attrs) {
  5. this(context, attrs, 0);
  6. }
  7. public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  8. super(context, attrs, defStyleAttr);
  9. }

二.自定义命名空间:

      当我们自定义View的时候许多的属性我们当然不希望被写死。例如我自定义了一个圆,这个圆的颜色我更希望不在自定义控件里面写死。而是在使用的时候在布局文件中进行指定这个颜色的时候,我们就需要用到自定义命名空间来对自定义View的属性进行设置了。

步骤1首先在values文件夹里面新建attrs文件。

                          

步骤2:编写attrs文件,attrs有两种写法.
            ( 1.)针对于单个View自己去定义不同的属性。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <!--属性申明必须是自定义控件的名称-->
  4. <declare-styleable name="MyView">
  5. <!--采用驼峰命名规则,可以随意命名,format是属性的单位-->
  6. <attr name="roundColor" format="color"></attr>
  7. </declare-styleable>
  8. </resources>

              ( 2.)如果是有公共的属性部分,可以将属性包含在公共属性部分里面.也就是说公共属性可以被多个自定义控件属性样式使用。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <!--采用驼峰命名规则,可以随意命名,format是属性的单位-->
  4. <attr name="roundColor" format="color"></attr>
  5. <!--属性申明必须是自定义控件的名称-->
  6. <declare-styleable name="MyView">
  7. <attr name="roundColor"></attr>
  8. </declare-styleable>
  9. </resources>

  所有的format类型

  • reference     引用
  • color            颜色
  • boolean       布尔值
  • dimension   尺寸值
  • float            浮点值
  • integer        整型值
  • string          字符串
  • enum          枚举值

步骤3:在布局文件中对自定义的属性进行使用。
           打开布局文件我们可以看到有很多的以xmlns开头的字段。其实这个就是XML name space 的缩写。我们仿照系统定义好的自己来定义一个命名空间。   

android studio写法:          

      xmlns:app="http://schemas.android.com/apk/res-auto"  

eclipse写法:             

xmlns:app="http://schemas.android.com/apk/res/com.dapeng.viewdemo"
  • xmlns:上面说过了是XML name space 的缩写。
  • app  :是命名空间的名称可随意书写。
  • com.dapeng.viewdemo为本应用的包名。


步骤4:在自定义View中将我们定义好的属性拿到,通过context.obtainStyledAttributes将构造函数中的attrs进行解析出来,就可以拿到相对应的属性。

  1. public MyView(Context context) {
  2. super(context);
  3. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
  4. int color = typedArray.getColor(R.styleable.MyView_roundColor, 0XFF00FF00);
  5. typedArray.recycle();//一定要记得关闭
  6. }

需要注意是:在我们获取尺寸的时候有三个函数进行使用,我们来看看他们之间的区别。

  • getDimension()是基于当前DisplayMetrics进行转换,获取指定资源id对应的尺寸。文档里并没说这里返回的就是像素,要注意这个函数的返回值是float,像素肯定是int。
  • getDimensionPixelSize()与getDimension()功能类似,不同的是将结果转换为int,并且小数部分四舍五入。
  • getDimensionPixelOffset()与getDimension()功能类似,不同的是将结果转换为int,并且偏移转换(offset conversion,函数命名中的offset是这个意思)是直接截断小数位,即取整(其实就是把float强制转化为int,注意不是四舍五入哦)。

由此可见,这三个函数返回的都是绝对尺寸,而不是相对尺寸(dp\sp等)。如果getDimension()返回结果是20.5f,那么getDimensionPixelSize()返回结果就是21,getDimensionPixelOffset()返回结果就是20。

理论知识讲的有点多,可能有点空洞,下面通过一个小的例子,来测试一下我们的命名空间是否可以正常使用.

例子:我们自定义一个View,这个View的形状是一个圆形,并且我们不希望将圆的颜色写死,可以在布局文件中进行设置。

其他的我们都先不管,只是测试一下自定义命名空间。

步骤1:创建一个View继承自View.并且重写它的构造函数。

  1. public class MyView extends View {
  2. public MyView(Context context) {
  3. this(context, null);
  4. }
  5. public MyView(Context context, AttributeSet attrs) {
  6. this(context, attrs, 0);
  7. }
  8. public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  9. super(context, attrs, defStyleAttr);
  10. }
  11. }

 步骤2:在Values文件夹下面创建一个attrs的文件,写上自定义的属性. 。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <!--属性申明必须是自定义控件的名称-->
  4. <declare-styleable name="MyView">
  5. <!--采用驼峰命名规则,可以随意命名,format是属性的单位-->
  6. <attr name="roundColor" format="color"></attr>
  7. <attr name="radius" format="dimension"></attr>
  8. </declare-styleable>
  9. </resources>

步骤3:在布局文件中进行引用自定义命名空间,这里给设置的颜色是橘黄色。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context="com.dapeng.viewdemo.MainActivity">
  9. <com.dapeng.viewdemo.MyView
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:background="@android:color/holo_red_light"
  13. app:roundColor="@android:color/holo_blue_bright"
  14. app:radius="50dp"
  15. />
  16. </RelativeLayout>

步骤4:在自定义View中设置我们自定义的属性。

  1. public class MyView extends View {
  2. private int mColor;
  3. private Paint mP;
  4. private float mRadius;
  5. public MyView(Context context) {
  6. this(context, null);
  7. }
  8. public MyView(Context context, AttributeSet attrs) {
  9. this(context, attrs, 0);
  10. }
  11. public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  12. super(context, attrs, defStyleAttr);
  13. //拿到自定义属性
  14. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
  15. mColor = typedArray.getColor(R.styleable.MyView_roundColor, 0XFF00FF00);
  16. mRadius = typedArray.getDimension(R.styleable.MyView_radius, 50);
  17. //回收资源
  18. typedArray.recycle();
  19. //创建画笔
  20. mP = new Paint();
  21. //设置画笔颜色
  22. mP.setColor(mColor);
  23. //设置抗锯齿
  24. mP.setAntiAlias(true);
  25. }
  26. @Override
  27. protected void onDraw(Canvas canvas) {
  28. super.onDraw(canvas);
  29. //画圆
  30. canvas.drawCircle(mRadius, mRadius, mRadius, mP);
  31. }
  32. }

Ok代码书写完毕,我们来看看实现的效果是怎么样的。
                              

如果将颜色设置为蓝色就是如下效果:

                                              

当然,这里只是实现了自定义控件的一小部分功能,接着我们来看看一个问题:我们将我们自定义控件的background设置为红色来看看效果,这里控件都是设置包裹内容的。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context="com.dapeng.viewdemo.MainActivity">
  9. <com.dapeng.viewdemo.MyView
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:background="@android:color/holo_red_light"
  13. app:roundColor="@android:color/holo_blue_bright"
  14. />
  15. </RelativeLayout>

效果如下图所示:

看到这个效果可能会有点奇怪了,明明我设置的控件大小是包裹内容的,为什么控件确占满了整个父窗体?带着这样的疑问我们接下来学习,自定义控件的另外一个非常重要的函数:onmeasure()。

三.onMeasure()测量。

如果自定义View时,layout_widht和layout_height是match_parent或具体的xxxdp,说明此时的View的大小是确定的,我们可以直接调用setMeasuredDimension()方法,设置ViewGroup的宽高即可。如果是view的宽高都是wrap_content,如果不重写 onMeasure() 方法,系统则会不知道该默认多大尺寸,就会默认填充整个父布局,所以,重写 onMeasure() 方法的目的,主要的作用就是处理自定义VIew的时候如果宽高是wrap_content的时候,确定该View的大小的功能。

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. }

调用此方法会传进来的两个参数:int widthMeasureSpec,int heightMeasureSpec。

他们是父类传递过来给当前view的一个建议值。即把当前view的尺寸设置为宽widthMeasureSpec,高heightMeasureSpec。虽然表面上看起来他们是int类型的数字,其实他们是由mode+size两部分组成的。 widthMeasureSpec和heightMeasureSpec转化成二进制数字表示,他们都是30位的。前两位代表mode(测量模   式),后面28位才是他们的实际数值(size)。 

  1. MeasureSpec.getMode()获取模式
  2. MeasureSpec.getSize()获取尺寸  

 mode的值有三种为:

  • EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值(也就是我们在布局文件中设定的值如50dp)、match_parent时,ViewGroup会将其设置为EXACTLY;
  • AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
  • UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。

我们需要判断当布局文件中设置控件为包裹内容的时候,控件的大小的值就可以了。因此重写onmeasure()方法如下:

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. //获取测量的模式
  5. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  6. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  7. //获取测量的值
  8. int withSize = MeasureSpec.getSize(widthMeasureSpec);
  9. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  10. //设置控件的大小
  11. setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? (int) mRadius * 2 : withSize, heightMode == MeasureSpec.AT_MOST ? (int) mRadius * 2 : heightSize);
  12. }

设置成功以后,直接将工程运行起来就可以看到效果了:

上面的例子都是演示的画圆。如果想画其他的形状应该怎么办呢?我们需要通过重写onDraw() 方法对控件重写进行绘制就可以了。

四.onSizeChanged

在 onSizeChanged() 方法调用后,View 的宽高也就确定了。

五.View的绘制.

1.Ondraw()方法。

draw就是画的意思从字面意思我们也可以知道,通过重写该方法我们可以对绘制出相关的控件。那么绘制的时候在是在什么上面进行绘制呢?我们先来重写ondraw()看看:

  1. Paint paint = new Paint();
  2. @Override
  3. protected void onDraw(Canvas canvas) {
  4. super.onDraw(canvas);
  5. // 绘制一个圆
  6. canvas.drawCircle(100, 100, 100, paint);
  7. }

我们可以看到通过重写ondraw()会传过来一个Canvas类,这个类实际上就是一块儿画布,我们可以创建画笔在上面进行绘图。

2.Canvas画布。

Canvas相当于一个画布,你可以在里面画很多东西。Canvas可以绘制的对象有:弧线(arcs)、填充颜色(argb和color)、 Bitmap、圆(circle和oval)、点(point)、线(line)、矩形(Rect)、图片(Picture)、圆角矩形 (RoundRect)、文本(text)、顶点(Vertices)、路径(path)。通过组合这些对象我们可以画出一些简单有趣的界面出来,但是光有这些功能还是不够的,如果我要画一个仪表盘(数字围绕显示在一个圆圈中)呢? 幸好Android还提供了一些对Canvas位置转换的方法:rorate、scale、translate、skew(扭曲)等,而且它允许你通过获得它的转换矩阵对象(getMatrix方法) 直接操作它。这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。为了方便一些转换操作,Canvas 还提供了保存和回滚属性的方法(save和restore),比如你可以先保存目前画纸的位置(save),然后旋转90度,向下移动100像素后画一些图形,画完后调用restore方法返回到刚才保存的位置。

(1.)canvas画简单的图形。

画圆:canvas.drawCircle(float centerX, float centerY, float radius,Paint paint):前两个参数 centerX centerY 是圆心的X轴和Y轴坐标,第三个参数 radius 是圆的半径,单位都是像素。

 canvas.drawCircle(100, 100, 90, paint);   

画弧形:canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint);

  1. //绘制弧线区域
  2. //先要绘制矩形
  3. RectF rect = new RectF(0, 0, 100, 100);
  4. canvas.drawArc(rect, //弧线所使用的矩形区域大小
  5. 270, //弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度)
  6. 90, //扫过的角度
  7. true, //是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形
  8. paint); //画笔

颜色填充:canvas.drawColor(int color):会把整个区域染成纯色,覆盖掉原有内容; 

 canvas.drawColor(Color.BLUE);   

画一条线:canvas.drawLine():分别是线的起点和终点坐标。

  1. canvas.drawLine(10,//x起点位置
  2. 10, //y起点位置
  3. 100, //x终点位置
  4. 100, //y终点位置
  5. paint); //画笔

画多条直线:drawLines(float[] pts, int offset, int count, Paint paint) 

画多条直线:drawLines(float[] pts, Paint paint)

 

画椭圆:canvas.drawOval(oval, paint);

  1. //定义一个矩形区域
  2. RectF oval = new RectF(0,0,200,300);
  3. //矩形区域内切椭圆
  4. canvas.drawOval(oval, paint);

画带有弧度的文字:canvas.drawPosText();

  1. //按照既定点 绘制文本内容
  2. canvas.drawPosText("Android", new float[]{
  3. 10,10, //第一个字母在坐标10,10
  4. 20,20, //第二个字母在坐标20,20
  5. 30,30, //....
  6. 40,40,
  7. 50,50,
  8. 60,60,
  9. 70,70,
  10. }, paint);

画矩形:canvas.drawRect():矩形四条边的坐标。

  1. RectF rect = new RectF(50, 50, 200, 200);
  2. canvas.drawRect(rect, paint);

画带有弧度的矩形:canvas.drawRoundRect():lefttoprightbottom 是四条边的坐标,rx 和 ry 是圆角的横向半径和纵向半径

  1. RectF rect = new RectF(50, 50, 200, 200);
  2. canvas.drawRoundRect(rect,
  3. 30, //x轴的半径
  4. 30, //y轴的半径
  5. paint);

(1.)canvas画复杂的图形。Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。

lineTo (float x, float y):从某个点到参数坐标点之间连一条线。

moveTo (float x, float y):移动下一次操作的起点位置,只改变下次操作的起点。

rMoveTo(float x, float y)/rLineTo(float x, float y):不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)。

reset():清除Path中的内容。

setLastPoint (float dx, float dy):设置之前操作的最后一个点位置。

close ():用于连接当前最后一个点和最初的一个点(如果两个点不重合的话),最终形成一个封闭的图形。

addXxx()在Path中添加基本图形:

画封闭的图形:

  1. Path path = new Path(); //定义一条路径
  2. path.moveTo(10, 10); //移动到 坐标10,10
  3. path.lineTo(50, 60);
  4. path.lineTo(200,80);
  5. path.lineTo(10, 10);
  6. canvas.drawPath(path, paint);

画文字跟随一条线:

  1. Path path = new Path(); //定义一条路径
  2. path.moveTo(10, 10); //移动到 坐标10,10
  3. path.lineTo(50, 60);
  4. path.lineTo(200,80);
  5. path.lineTo(10, 10);
  6. canvas.drawTextOnPath("Android", path, 10, 10, paint);

画图片:

drawBitmap(Bitmap bitmap, float left, float top, Paint paint)

canvas的操作方法:

canvas.translate(float dx, float dy):移动坐标系到到x,y坐标点,位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动。

public void scale (float sx, float sy) / public final void scale (float sx, float sy, float px, float py):将画纸缩放,x轴和y轴的缩放比例,px和py用来控制缩放中心位置。

public void rotate (float degrees) / public final void rotate (float degrees, float px, float py):将画纸进行旋转一定的角度,默认的旋转中心依旧是坐标原点,degrees是旋转的角度,px和py是控制旋转中心点的位置。

  1. canvas.rotate(360 / count,//旋转的角度
  2. 0f, x轴的坐标
  3. 0f); //旋转画纸
  4. canvas.translate(200, 200); //将位置移动画纸的坐标点到x为200,y为200
  5. canvas.save(); //保存画布的状态
  6. canvas.restore(); //回滚画布的状态

canvas.save()和canvas.restore()是两个相互匹配出现的,作用是用来保存画布的状态和取出保存的状态的。

当我们对画布进行旋转,缩放,平移等操作的时候其实我们是想对特定的元素进行操作,比如图片,一个矩形等,但是当你用canvas的方法来进行这些操作的时候,其实是对整个画布进行了操作,那么之后在画布上的元素都会受到影响,所以我们在操作之前调用canvas.save()来保存画布当前的状态,当操作之后取出之前保存过的状态,这样就不会对其他的元素进行影响.

3.画笔Paint

画笔有三种模式:

  1. STROKE //描边
  2. FILL //填充
  3. FILL_AND_STROKE //描边加填充

  从上面列举的几个Canvas.drawXxx()的方法看到,其中都有一个类型为paint的参数,可以把它理解为一个"画笔",通过这个画笔,在Canvas这张画布上作画。 它位于"android.graphics.Paint"包下,主要用于设置绘图风格,包括画笔颜色、画笔粗细、填充风格等。

  Paint中提供了大量设置绘图风格的方法,这里仅列出一些常用的:

  • setARGB(int a,int r,int g,int b):设置ARGB颜色。
  • setColor(int color):设置颜色。
  • setAlpha(int a):设置透明度。
  • setPathEffect(PathEffect effect):设置绘制路径时的路径效果。
  • setShader(Shader shader):设置Paint的填充效果。
  • setAntiAlias(boolean aa):设置是否抗锯齿。
  • setStrokeWidth(float width):设置Paint的笔触宽度。
  • setStyle(Paint.Style style):设置Paint的填充风格。
  • setTextSize(float textSize):设置绘制文本时的文字大小。

 

4.invalidate()和postInvalidate()的区别

通过上面的讲解,我在自定义一个静态的View已经是一件非常容易的事情了。但是我们使用的自定义的View有很多是需要根据一个变量去不断绘制的,这个时候就引出了新的函数invalidate()和postinvalidate(),使用此函数可以使得ondraw()不断的去执行从而达到不断绘制的效果。

Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。

接下来通过一个稍微综合一点的例子来对自定义View做一个总结:

                            

我们先来分析一下,这个效果实际上就是外面是不断的去绘制一个扇形,然后中间盖了一个小的圆:

好了,接下来我们来讲一讲实现的步骤:

步骤一:首先定义attrs文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="ProgressView">
  4. <attr name="smallRoundColor" format="color"></attr>
  5. <attr name="smallRoundRadius" format="dimension"></attr>
  6. <attr name="arcRadius" format="dimension"></attr>
  7. <attr name="arcColor" format="color"></attr>
  8. <attr name="textColor" format="color"></attr>
  9. <attr name="textSize" format="dimension"></attr>
  10. </declare-styleable>
  11. </resources>

步骤二:编写自定义View:

  1. public class ProgressView extends View {
  2. private float mArcRadius;
  3. private float mSmallRoundRadius;
  4. private int mArcColor;
  5. private int mSmallRoundColor;
  6. private Paint mRoundpaint;
  7. private Paint mArcpaint;
  8. private float sweepAngle;
  9. private int mTextColor;
  10. private Paint mTextPaint;
  11. private int mTextSize;
  12. public ProgressView(Context context) {
  13. this(context, null);
  14. }
  15. public ProgressView(Context context, AttributeSet attrs) {
  16. this(context, attrs, 0);
  17. }
  18. public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
  19. super(context, attrs, defStyleAttr);
  20. TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ProgressView);
  21. //扇形半径
  22. mArcRadius = array.getDimension(R.styleable.ProgressView_arcRadius, 50);
  23. //小圆半径
  24. mSmallRoundRadius = array.getDimension(R.styleable.ProgressView_smallRoundRadius, 50);
  25. //扇形颜色
  26. mArcColor = array.getColor(R.styleable.ProgressView_arcColor, 0XFF00FF00);
  27. //小圆颜色
  28. mSmallRoundColor = array.getColor(R.styleable.ProgressView_smallRoundColor, 0XFF00FF00);
  29. //百分比字体颜色
  30. mTextColor = array.getColor(R.styleable.ProgressView_textColor, 0XFF00FF00);
  31. //百分比字体大小
  32. mTextSize = array.getDimensionPixelSize(R.styleable.ProgressView_textSize, 15);
  33. //释放资源
  34. array.recycle();
  35. //画圆的画笔
  36. mRoundpaint = new Paint();
  37. mRoundpaint.setColor(mSmallRoundColor);
  38. mRoundpaint.setAntiAlias(true);
  39. //画扇形的画笔
  40. mArcpaint = new Paint();
  41. mArcpaint.setColor(mArcColor);
  42. mArcpaint.setAntiAlias(true);
  43. //绘制中间文字部分的画笔文本
  44. mTextPaint = new Paint();
  45. mTextPaint.setColor(mTextColor);
  46. mTextPaint.setAntiAlias(true);
  47. mTextPaint.setTextSize(mTextSize);
  48. }
  49. @Override
  50. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  51. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  52. //设置View的大小
  53. int withMode = MeasureSpec.getMode(widthMeasureSpec);
  54. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  55. int withSize = MeasureSpec.getSize(widthMeasureSpec);
  56. int heightsize = MeasureSpec.getSize(heightMeasureSpec);
  57. setMeasuredDimension(withMode == MeasureSpec.AT_MOST ? (int) mArcRadius * 2 : withSize, heightMode == MeasureSpec.AT_MOST ? (int) mArcRadius * 2 : heightsize);
  58. }
  59. @Override
  60. protected void onDraw(Canvas canvas) {
  61. //画圆弧
  62. RectF rect = new RectF(0, 0, (int) mArcRadius * 2, (int) mArcRadius * 2);
  63. canvas.drawArc(rect, 270, (float) (sweepAngle * 3.6), true, mArcpaint);
  64. //画小圆
  65. canvas.drawCircle(mArcRadius, mArcRadius, mSmallRoundRadius, mRoundpaint);
  66. String text = (int) (sweepAngle) + "%";
  67. float textLength = mTextPaint.measureText(text);
  68. //把文本画在圆心居中
  69. canvas.drawText(text, mArcRadius - textLength / 2, mArcRadius, mTextPaint);
  70. super.onDraw(canvas);
  71. }
  72. //提供一个给外界的方法可以不断的去设置扇形的弧度
  73. public void percent(float sweepAngle) {
  74. if (sweepAngle <= 100) {
  75. this.sweepAngle = sweepAngle;
  76. //刷新界面
  77. postInvalidate();
  78. }
  79. }
  80. }

步骤三:在布局文件中进行使用:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context="com.dapeng.viewdemo.SecondActivity">
  9. <com.dapeng.viewdemo.ProgressView
  10. android:id="@+id/pv"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. app:arcColor="@android:color/holo_blue_bright"
  14. app:arcRadius="120dp"
  15. app:textSize="20sp"
  16. app:smallRoundColor="@android:color/transparent"
  17. app:textColor="@android:color/holo_orange_light"
  18. app:smallRoundRadius="100dp"/>
  19. <Button
  20. android:id="@+id/btn"
  21. android:text="开始绘制"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:layout_centerInParent="true"/>
  25. </RelativeLayout>

步骤四:在需要用到的地方模拟数据去使用自定义的View.

  1. public class SecondActivity extends AppCompatActivity {
  2. private int mTotalProgress;
  3. private int mCurrentProgress;
  4. private ProgressView mPv;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_second);
  9. mPv = (ProgressView) findViewById(R.id.pv);
  10. initVariable();
  11. findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
  12. @Override
  13. public void onClick(View v) {
  14. mCurrentProgress=0;
  15. new Thread(new ProgressRunable()).start();
  16. }
  17. });
  18. }
  19. private void initVariable() {
  20. mTotalProgress = 100;
  21. mCurrentProgress = 0;
  22. }
  23. class ProgressRunable implements Runnable {
  24. @Override
  25. public void run() {
  26. while (mCurrentProgress < mTotalProgress) {
  27. mCurrentProgress += 1;
  28. mPv.percent((float) mCurrentProgress);
  29. try {
  30. Thread.sleep(50);
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. }
  37. }

六.Onlayout

layout()过程,对于View来说用来计算View的位置参数,对于ViewGroup来说,除了要测量自身位置,还需要测量子View的位置。一般在使用自定义ViewGroup用此函数比较多。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/673472
推荐阅读
相关标签
  

闽ICP备14008679号