以下均由源码改的不完全代码,本篇文章的目的是分析View的绘制流程,忽略计算细节,具体计算代码分析会在下篇。
ViewRootImpl
中的performTraversals
,有3个函数performMeasure
,performLayout
和 performDraw
。
View
的绘制流程是测量,布局和绘制,现实生活中画画的话,比如说你要画一片叶子。首先你需要一张纸,纸就类似于ViewGroup,当纸的大小是确定的情况下,接下来需要确定要叶子的大小,然后确定叶子在纸中的位置也就是布局情况,最后才能绘制图像,这里我先以measure
为切入点去分析。
建议,分析源码之前先了解设计模式:组合模式,模板方法模式。
这里需对组合模式有一定的了解才能理解View
和ViewGroup
之间的关系,并且能更好地理解Viewgroup遍历。
需要对模板方法模式有一定的了解才能理解onMeasure
方法的使用,onMeasure
的使用本质是钩子方法的使用。钩子方法的引入使得子类可以控制父类的行为。
先来定义ViewRootImpl
,View
和ViewGroup
。
//ViewRootImpl.java
public class ViewRootImpl {
private static final String TAG = "ViewRootImpl";
boolean mFullRedrawNeeded;
private int mWidth = -1;
private int mHeight = -1;
private View mView;
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
public void setView(View view) {
if (mView == null) {
mView = view;
}
}
void doTraversal() {
performTraversals();
}
private void performTraversals() {
WindowManager.LayoutParams lp = mWindowAttributes;
performMeasure(1, 1);
performLayout(lp, mWidth, mHeight);
performDraw();
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
if (host == null) {
return;
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
}
}
//View.java
public class View {
private static final String TAG = "View";
private Context mContext;
private Drawable mBackground;
private int mMinWidth;
private int mMinHeight;
int mMeasuredWidth;
int mMeasuredHeight;
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
public View(Context context){
mContext = context;
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(1,1);
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
Log.d(TAG," mMeasuredWidth = " + mMeasuredWidth + " , mMeasuredHeight = " + mMeasuredHeight);
}
public void layout(int l, int t, int r, int b) {
}
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
}
public abstract class ViewGroup extends View {
private static final String TAG = "View";
// Child views of this ViewGroup
private View[] mChildren;
private int mChildrenCount;
private static final int ARRAY_INITIAL_CAPACITY = 12;
private static final int ARRAY_CAPACITY_INCREMENT = 12;
private int mLastTouchDownIndex = -1;
public ViewGroup(Context context) {
super(context);
mChildren = new View[ARRAY_INITIAL_CAPACITY];
mChildrenCount = 0;
}
public int getChildCount() {
return mChildrenCount;
}
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
public void addView(View child, int index) {
addViewInner(child, index);
}
private void addViewInner(View child, int index) {
addInArray(child, index);
}
private void addInArray(View child, int index) {
View[] children = mChildren;
final int count = mChildrenCount;
final int size = children.length;
if (index == count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
children[mChildrenCount++] = child;
} else if (index < count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
children = mChildren;
} else {
System.arraycopy(children, index, children, index + 1, count - index);
}
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
} else {
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}
}
由于ViewGroup
是抽象类,不能直接作为根节点使用,所以新建一个类继承ViewGroup
,这里命名为FrameLayout
。这里重写了onMeasure
。
public class FrameLayout extends ViewGroup{
public FrameLayout(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//measure(1, 1)是随便赋值的。
// 通过调用child.measure去遍历ViewGroup下的所有节点的大小
child.measure(1, 1);
}
}
}
下面再构建两个View节点。这里可以命名为TextView
和ImageView
。
public class TextView extends View {
private static final String TAG = "TextView";
public TextView(Context context){
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width;
int height;
//这中间略过了根据widthMeasureSpec和heightMeasureSpec等参数计算width, height的过程
// 就当计算出的结果为10,因为本文目的是梳理大致流程
width = 10;
height = 10;
//计算出实际的width, height然后存储
setMeasuredDimension(width, height);
}
}
public class ImageView extends View {
private static final String TAG = "ImageView";
public ImageView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width;
int height;
//略过了根据widthMeasureSpec和heightMeasureSpec等参数计算width, height的过程
width = 20;
height = 20;
//计算出实际的width, height然后存储
setMeasuredDimension(width, height);
}
}
调用如下。先把TextView
和ImageView
添加到FrameLayout
中,然后通过调用setView
传递到ViewRoot
中,然后调用doTraversal
,再走到performMeasure
,最后mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
。这个mView
就是mFrameLayout
。由于FrameLayout
中重写了onMeasure
,所以执行mFrameLayout.measure
会遍历测量子View的大小。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ViewRootImpl mViewRoot;
private FrameLayout mFrameLayout;
private TextView mTextView;
private ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFrameLayout = new FrameLayout(this);
mImageView = new ImageView(this);
mTextView = new TextView(this);
mFrameLayout.addView(mTextView,0);
mFrameLayout.addView(mImageView,1);
mViewRoot = new ViewRootImpl();
mViewRoot.setView(mFrameLayout);
mViewRoot.doTraversal();
}
}
运行后Log如下。
View: mMeasuredWidth = 10 , mMeasuredHeight = 10
D/View: mMeasuredWidth = 20 , mMeasuredHeight = 20
ViewRootImpl
与View
是聚合关系。
View
和ViewGroup
是依赖 + 自关联。
关系图如下:
这里
View
和ViewGroup
首先是用到了设计模式中的组合模式,组合模式是一种结构型模式,用来处理树形结构。设计模式的艺术软件开发人员内功修炼之道
整体流程如果再加上performLayout
和performDraw
,那么调用流程就基本上是下面的图。但是performTraversals
、performMeasure
、performLayout
以及performDraw
不属于ViewGroup
,应该加到ViewRootImpl
里面去。也可以想象成把上面的图中的Measure
文字换成Layout
。
参考链接:
View的绘制流程(三):ViewRootImpl.performTraversals()方法
performTraversals()分析
Android视图框架Activity,Window,View,ViewRootImpl理解
Android 源码分析 - View的measure、layout、draw三大流程
浅析Android View的Measure过程
Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)
从数据结构与算法以及设计模式角度去学习View的绘制流程
Android 之 ViewTreeObserver 全面解析
从Android源码分析View绘制流程
通过抽象的方式来讲一讲View的绘制流程
源码分析篇 - Android绘制流程(二)measure、layout、draw流程
深入理解MeasureSpec
addView方法分析
网友评论