android 自定义View原理详解01

来源:互联网 发布:网络打印机主机有密码 编辑:程序博客网 时间:2024/06/06 11:00
视图绘制流程
Android中的任何一个布局、任何一个控件其实都是直接或间接继承自 View的,如 TextView Button ImageView ListView等,任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即 onMeasure() onLayout() onDraw(),下面我们逐个对这三个阶段展开进行探讨。
1 onMeasure()
measure是测量的意思,那么 onMeasure()方法顾名思义就是用于测量视图的大小的。 View系统的绘制流程会
ViewRoot performTraversals() 方法中开始,在其内部调用 View measure()方法。 measure()方法接收两个参数, widthMeasureSpec heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize specMode 共同组成的,其中 specSize记录的是大小, specMode记录的是规格。 specMode一共有三种类型,如下所示:

l EXACTLY :表示父视图希望子视图的大小应该是由 specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

l AT_MOST :表示子视图最多只能是 specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过 specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

l UNSPECIFIED:表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制。
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(200, 200);
}
}这样的话就把 View默认的测量流程覆盖掉了,不管在布局文件中定义 MyView这个视图的大小是多少,最终在界面上显示的大小都将会是 200*200
需要注意的是,在 setMeasuredDimension()方法调用之后,我们才能使用 getMeasuredWidth() getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是 0
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在 XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。

2 onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是 layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。 ViewRoot performTraversals()方法会在 measure结束后继续执行,并调用 View layout()方法来执行此过程
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。
重写onLayout()方法来自定义一个布局
public class SimpleLayout extends ViewGroup {
public SimpleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 0) {
View childView = getChildAt(0);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
}

onMeasure()方法会在onLayout() 方法之前调用,因此这里在 onMeasure()方法中判断 SimpleLayout中是否有包含一个子视图,如果有的话就调用 measureChild()方法来测量出子视图的大小。
接着在onLayout()方法中同样判断 SimpleLayout是否有包含一个子视图,然后调用这个子视图的 layout()方法来确定它在 SimpleLayout布局中的位置,这里传入的四个参数依次是 0 0 childView.getMeasuredWidth()childView.getMeasuredHeight() ,分别代表着子视图在 SimpleLayout中左上右下四个点的坐标。其中,调用 childView.getMeasuredWidth()childView.getMeasuredHeight() 方法得到的值就是在 onMeasure()方法中测量出的宽和高。
创建并使用自己的布局
<com.example.viewtest.SimpleLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
</com.example.viewtest.SimpleLayout>
3 onDraw()

measure layout的过程都结束后,接下来就进入到 draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。 ViewRoot中的代码会继续执行并创建出一个 Canvas对象,然后调用 View draw()方法来执行具体的绘制工作。
View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察 TextView ImageView等类的源码,你会发现它们都有重写 onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助 Canvas这个类,它会作为参数传入到 onDraw()方法中,供给每个视图使用。 Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西
public class MyView extends View {
private Paint mPaint;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);


}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(Color.BLUE);
mPaint.setTextSize(20);
String text = "Hello View";
canvas.drawText(text, 0, getHeight() / 2, mPaint);
}
}
我们创建了一个自定义的 MyView继承自 View,并在 MyView的构造函数中创建了一个 Paint对象。 Paint就像是一个画笔一样,配合着 Canvas就可以进行绘制了。这里我们的绘制逻辑比较简单,在 onDraw()方法中先是把画笔设置成黄色,然后调用 Canvas drawRect()方法绘制一个矩形。然后在把画笔设置成蓝色,并调整了一下文字的大小,然后调用 drawText()方法绘制了一段文字
测试自己定义的 View
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.viewtest.MyView
android:layout_width="200dp"
android:layout_height="100dp"
/>
</LinearLayout>


0 0
原创粉丝点击