Android自定义View

来源:互联网 发布:淘宝助手数据包 编辑:程序博客网 时间:2024/06/04 05:53

内容会很多哦。如果这节自定义View描述有什么错误的地方,麻烦指正一下。
如果不懂的同学,希望你们耐心看完本章节。我也是刚入门的。好了,废话不多说!!


Android自定义View 是什么?

顾名思义,自定义View就是我们自己定义的View,并且能和用户进行交互的控件。我们使用的button都是继承view这个父类的。当安卓内置的View不足以满足我们日常的开发需求的时候,我们就可以通过自定义View来实现自己想要的效果。比如如下图所示,跳过显示广告网页直接进入主界面的按钮。
这里写图片描述


涉及到的类和方法?

onDraw():是View显示界面的方法,在这个方法的内部进行描绘你想要的效果。一般都会涉及到几个比较重要的类。

  • Paint 画笔类,设置画笔的粗细,颜色等属性

  • Canvas 画布类,提供各种画各种图形的api

  • Bitmap 位图类,用来获取图片的信息。

  • matrix 矩阵类,用来控制图片的现实


View的绘制流程

假设一辆车的生产过程。

  • 测量车的框架大小,根据车的框架大小来测量车内饰大小的,比如座位等。onMeasure()

  • 开始定制,根据测量的尺寸来进行生产。onDraw()

  • 生产完成之后,将需要的东西填充到框架里去。onLayout()

  • 最后,该车具备了走的功能了,我们就能使用它了。onTouch() 用于和用户进行交互


初步使用?

首先我创建一个类,RectView类。这个类需要继承自View。其次覆写里面的方法,在这里我们覆写前面两种方法。这里写图片描述

public class RectView extends View {    public RectView(Context context) {        super(context);    }    public RectView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }}

我们首先简单的画一个矩形。此时我们需要覆写onDraw()方法。

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //创建一个矩形区域,里面的数字分别代表  left,top,right,bottom        RectF rectF = new RectF(0, 0, 100, 100);        //创建一个画笔        Paint paint = new Paint();        //用画布画出一个矩形        canvas.drawRect(rectF, paint);    }

然后添加一下xml的标签,相当于添加一个button按钮这样。

 <com.example.does.customview.RectView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        />

然后Run,这样矩形就画出来了。

这里写图片描述

onDraw(Canvas canvas)中的canvas相当于画布,不了解的话可以百度一下。理解为一张画布就行了。Paint()这个就是创建一个画笔,RectF()中的Rect代表的意思是矩形,F代表的float类型,可以Ctrl点进去看源码就知道了。
Canvas这个类里面有许多方法,比如画一个圆。我们修改一下,

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //创建一个矩形区域,里面的数字分别代表  left,top,right,bottom        RectF rectF = new RectF(0, 0, 500, 500);        //创建一个画笔        Paint paint = new Paint();        //用画布画出一个圆        canvas.drawOval(rectF,paint);    }

效果如下,
这里写图片描述

当然,我们还可以对其描绘的东西做其它属性的修改,

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //创建一个矩形区域,里面的数字分别代表  left,top,right,bottom        RectF rectF = new RectF(10, 10, 500, 500);        //创建一个画笔        Paint paint = new Paint();        //抗锯齿        paint.setAntiAlias(true);        //设置一个空心圆        paint.setStyle(Paint.Style.STROKE);        //设置画笔的颜色        paint.setColor(Color.BLUE);        //设置外边粗一点        paint.setStrokeWidth(5);        //用画布画出一个圆        canvas.drawOval(rectF,paint);    }

这里写图片描述

具体更多的属性,自己自定的过程中再摸索其里面更多的方法。这里再多做演示了。


自定义控件view覆写方法的调用顺序

添加需要实现的其它方法,并且Log日志打印

public class RectView extends View {    public RectView(Context context) {        super(context);    }    public RectView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        Log.i("does", "onDraw: ");    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.i("does", "onMeasure: ");    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        Log.i("does", "onLayout: ");    }}

运行,看输出日志

07-15 12:37:22.155 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.328 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.329 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onDraw:

显示测量两次,显示,再测量,最后绘画。
为什么这样?因为我们自定义View添加到xml布局文件里面去的,系统是先测量我们自定义控件的,宽度高度,然后再测量父控件的宽度高度,然后为了保险一点,系统再次测量我们的自定义控件的高度,最后再画出来。


onMeasure()方法讲解

我们先用一个实例看看具体有什么样的效果。
我在onDraw()里给canvas画一个颜色,然后在xml里面添加两个宽高属性,都是warp_content

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);       canvas.drawColor(Color.BLUE);    }
 <com.example.does.customview.RectView        android:layout_width="wrap_content"        android:layout_height="wrap_content" />

运行,效果如下。

这里写图片描述
如果我再修改xml的属性,将高度改成50dp,出现的是这样的效果,

 <com.example.does.customview.RectView        android:layout_width="wrap_content"        android:layout_height="50dp" />

这里写图片描述

接着,我们在onMeasure方法里添加一行代码

  setMeasuredDimension(100,100);

最终的效果是这样的
这里写图片描述

神奇吧 ?!! 哈哈。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

1.widthMeasureSpec
2.heightMeasureSpec

Log日志输入看看分别是什么值,将setMeasuredDimension(100,100);这行代码注释掉。

07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: -2147482568
07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: 1073741955

数值是什么我们暂且不管它。

这两个参数分别都是由两个int类型构成的。我们创建一个变量抽离出来,

        int widethMode = MeasureSpec.getMode(widthMeasureSpec);        int widethSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

打印log日志,

07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080

数值我们也暂且不管它。

我们看看getMode里面的源码,源码里定义了三个变量,分别是

  1. AT_MOST 表示父控件对自定义控件的最大值
  2. EXACTLY 表示父控件对自定义控件的精确值
  3. UNSPECIFIED 表示父控件对自定义控件大小无限制 比如listview

    log打印输出,

07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: UNSPECIFIED : 0
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: AT_MOST : -2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: EXACTLY : 1073741824

为了验证这三个值,我们在onMeasure()方法里添加switch语句。

  switch (widthMode){            case MeasureSpec.UNSPECIFIED:                Log.i("does", "UNSPECIFIED  : "+ MeasureSpec.UNSPECIFIED);                break;            case MeasureSpec.AT_MOST:                Log.i("does", "AT_MOST     : "+ MeasureSpec.AT_MOST );                break;            case MeasureSpec.EXACTLY:                Log.i("does", "EXACTLY      : "+ MeasureSpec.EXACTLY    );                break;            default:                break;        }

然后运行,发现输出语句

07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: AT_MOST : -2147483648

此时输出的是AT_MOST的值。

如果我们将布局文件里,我们自定义属性的宽高的wrap_content改成固定的值,输出的则是EXACTLY。精确值嘛,有确定的值。

那UNSPECIFIED呢?如果我们的父控件改成用scrollview,那么输出的值便是UNSPECIFIED。


onLayout()方法讲解

该方法是提供给ViewGroup来放置每个子控件的。通常我们需要配onMeasure来一起使用。
这个方法是每次布局大小变化,或者位置改变的时候都会本调用。
onLayout(boolean changed, int l, int t, int r, int b) changed 这个控件是否更改位置或者尺寸了
l 相对于父控件的左边的位置
t 相对于父控件的顶部的位置
r 相对于父控件的右边的位置
b 相对于父控件的下部的位置
我们必须在这个方法内部将子控件有序的放置在合适的位置。View 提供了llayout(int l, int t, int r, int b)方法给我们。