本篇分析performLayout这一步的过程。
先回顾下ViewRootImpl 。
import android.view.WindowManager;
public class ViewRootImpl {
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()
performLayout(lp, mWidth, mHeight);
}
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());
}
}
public class View {
int mMeasuredWidth;
int mMeasuredHeight;
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
/**
* The distance in pixels from the left edge of this view's parent
* to the left edge of this view.
* {@hide}
*/
protected int mLeft;
protected int mRight;
protected int mTop;
protected int mBottom;
protected int mPaddingLeft = 0;
protected int mPaddingRight = 0;
protected int mPaddingTop;
protected int mPaddingBottom;
protected ViewGroup.LayoutParams mLayoutParams;
public int mPrivateFlags;
static final int PFLAG_FORCE_LAYOUT = 0x00001000;
static final int PFLAG_LAYOUT_REQUIRED = 0x00002000;
final RenderNode mRenderNode;
public View(Context context) {
mRenderNode = RenderNode.create(getClass().getName());
}
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
return changed;
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
public ViewGroup.LayoutParams getLayoutParams() {
return mLayoutParams;
}
}
首先需要知道mLeft, mTop, mRight, mBottom。
mLeft是View左上角距父View左侧的距离
mTop是View左上角距父View顶部的距离
mRight是View右下角距父View顶部的距离
mBottom是View右下角距父View左侧的距离
然后还有一个掩码的概念(PFLAG_LAYOUT_REQUIRED)。
掩码(英语:Mask)在计算机学科及数字逻辑中指的是一串二进制数字,通过与目标数字的按位操作,达到屏蔽指定位而实现需求。
创造一个掩码msk把一个指令cmd的第0~3位(右边第一位为0位)清零:
指令cmd = 0110011011
创造掩码msk = 0000001111
用掩码的反码~msk和指令cmd做按位与运算cmd & ~msk = 0110011011 & 1111110000 = 0110010000
则指定的第0~3位已被清零。
RenderNode也是一个概念不太清晰的类。
public class RenderNode {
public final long mNativeRenderNode;
private RenderNode(String name) {
mNativeRenderNode = nCreate(name);
}
public static RenderNode create(String name) {
return new RenderNode(name);
}
public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
}
private static native long nCreate(String name);
private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
int right, int bottom);
}
//android_view_RenderNode.cpp
namespace android {
using namespace uirenderer;
static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
RenderNode* renderNode = new RenderNode();
renderNode->incStrong(0);
if (name != NULL) {
const char* textArray = env->GetStringUTFChars(name, NULL);
renderNode->setName(textArray);
env->ReleaseStringUTFChars(name, textArray);
}
return reinterpret_cast<jlong>(renderNode);
}
// ----------------------------------------------------------------------------
// RenderProperties - setters
// ----------------------------------------------------------------------------
static jboolean android_view_RenderNode_setLeftTopRightBottom(jlong renderNodePtr,
int left, int top, int right, int bottom) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) {
renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
return true;
}
return false;
}
};
//RenderNode.h
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
friend class FrameBuilder;
void setPropertyFieldsDirty(uint32_t fields) {
mDirtyPropertyFields |= fields;
}
}
RenderNode,render渲染,node,节点。从c文件可知,mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
是将只是将由mLeft, mTop, mRight, mBottom组成的区域存储到mDirtyPropertyFields。
虽然View中的onLayout没有具体实现,但是一般View不重写onLayout。除了TextView中重写了,ImageView,Button等都没有重写。
public class TextView extends View{
public TextView(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mDeferScroll >= 0) {
int curs = mDeferScroll;
mDeferScroll = -1;
bringPointIntoView(Math.min(curs, mText.length()));
}
// Call auto-size after the width and height have been calculated.
autoSizeText();
}
}
public abstract class ViewGroup extends View{
// Child views of this ViewGroup
private View[] mChildren = new View[2];
public ViewGroup(Context context) {
super(context);
}
public void addView(View child1,View child2) {
mChildren[0] = child1;
mChildren[1] = child2;
}
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
public static class LayoutParams{
public static final int FILL_PARENT = -1;
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
public int width;
public int height;
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
public LayoutParams(ViewGroup.LayoutParams source) {
this.width = source.width;
this.height = source.height;
}
/**
* Used internally by MarginLayoutParams.
* @hide
*/
LayoutParams() {
}
}
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
public int leftMargin;
public int topMargin;
public int rightMargin;
public int bottomMargin;
public MarginLayoutParams(ViewGroup.MarginLayoutParams source) {
this.width = source.width;
this.height = source.height;
this.leftMargin = source.leftMargin;
this.topMargin = source.topMargin;
this.rightMargin = source.rightMargin;
this.bottomMargin = source.bottomMargin;
}
}
View getVirtualChildAt(int index) {
return getChildAt(index);
}
public View getChildAt(int index) {
if (index < 0 || index >= 2) {
return null;
}
return mChildren[index];
}
}
public class LinearLayout extends ViewGroup {
private int mOrientation;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private int mGravity = Gravity.START | Gravity.TOP;
private int mTotalLength;//measure的时候这个值 会具体赋值
/**
* Horizontal layout direction is from Left to Right.
*/
public static final int LTR = 0;
/**
* Horizontal layout direction is from Right to Left.
*/
public static final int RTL = 1;
public LinearLayout(Context context){
super(context);
mOrientation = 1;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = 2;//getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop , childWidth, childHeight);
childTop += childHeight + lp.bottomMargin;
}
}
void layoutHorizontal(int left, int top, int right, int bottom) {
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public float weight;
public int gravity = -1;
public LayoutParams(LinearLayout.LayoutParams source) {
super(source);
this.weight = source.weight;
this.gravity = source.gravity;
}
}
public int getLayoutDirection() {
return LTR;
}
}
最后使用。
public class MainActivity extends AppCompatActivity {
private ViewRootImpl mViewRoot;
private LinearLayout mLinearLayout;
private TextView mTextView1;
private TextView mTextView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewRoot = new ViewRootImpl();
mLinearLayout = new LinearLayout(this);
mTextView1 = new TextView(this);
mTextView2 = new TextView(this);
mLinearLayout.addView(mTextView1,mTextView2);
mViewRoot.setView(mLinearLayout);
mViewRoot.doTraversal();
}
}
ImageView
等View
的onLayout
不会被重写(例如等)并且onLayout
内容为空,而LinearLayout
等ViewGroup
的onLayout
会被重写,用于定义ViewGroup
下的child
的坐标。
View
和ViewGroup
共用layout
。layout
用于设置View
和ViewGroup
等View的坐标。
主要需要分析一下LinearLayout 的onLayout过程。
假设现布局文件如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:layout_gravity="center_horizontal"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
/>
</LinearLayout>
mOrientation == VERTICAL
,因此调用layoutVertical
.
传过来的数据为left= 0,top = 0,right = 100,bottom = 100,mPaddingTop= 0,mPaddingRight = 10,mPaddingRight= 10那么width = 100,childRight = 90.childSpace = 80
mTotalLength
在LinearLayout
调用measure
的时候这个值 会具体赋值 这里为子view
们的总长度加上padding的值,某些情况下还有加上margin
的值(例如LinearLayout设置大小为wrap_content ),这里由于定义了TextView的
大小,并且LinearLayout
的mPaddingTop
和mPaddingBottom
为0,所以mTotalLength = 50.
由于LinearLayout
设置的gravity
属性为Gravity.CENTER_VERTICAL
,那么childTop = 0 + (100-0-50)/2 = 25
进入for循环后,走到case Gravity.CENTER_HORIZONTAL
。测量后childWidth = 50
,child
设置的leftMargin
和rightMargin
为10,因此childLeft = 10 + (80-50)/2 + 10-10 = 25。
因此childLeft = 25 childTop = 25 childWidth = 50 childHeight = 50
.
最后调用 child.layout(left, top, left + width, top + height);
即child.layout(25,25,75,75)
.
最终会调用View中的setFrame(l, t, r, b)
,将(25,25,75,75)存入mRenderNode。
调用performLayout是将ViewGroup和View的Left,Top,Right,Bottom四个数据存入RenderNode,设置RenderNode的 mDirtyPropertyFields。
具体是如何渲染的呢?这里面就又涉及到计算机图形学了。。这真是个绕不开的门槛。先略过,接下来是draw的流程。
参考链接:
自定义View原理篇(2)- layout过程
Android 4.3中的视觉边界布局(Optical bounds layout)
掩码
自定义 view - 布局 onLayout
Android 重学系列 View的绘制流程(六) 硬件渲染(上)
Android 系统架构 —— View 的硬件渲染
渲染基础-渲染管线(Render-pipeline)
网友评论