android 自定义View原理详解01

视图绘制流程
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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值