您的位置:首页 > 产品设计 > UI/UE

Android GUI之View布局

2015-08-17 14:17 591 查看
  在清楚了View绘制机制中的第一步测量之后,我们继续来了解分析View绘制的第二个过程,那就是布局定位。继续跟踪分析源码,根据之前的流程分析我们知道View的绘制是从RootViewImpl的performTraversals方法开始的,在此方法中依次调用了performMeasure、performLayout、performDraw等方法进行测量、布局、绘制,那么下面我们就看看则方performLayout中都做了哪些事情,该方法的关键源码如下:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
……
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
……
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}


  从中看出,最关键的代码就是调用了host.layout方法,那么大家还记不记得host是个什么东东呢?对了,正是我们之前说的根视图DecorView。那么我们就回到DecorView看看它在layout方法中到底做了什么事情。令人失望的是,我们在DecorView中并没有发现该方法,不要急,根据该类的继承体系,我们最终追踪到layout方法在View中。



public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}


  该方法中的4个参数代表了当前的View与父View之间4个方向上的距离,同时从说明中可以看出,此方法不应该被子类重写,如果需要重新布局,可以在子类中重写的方法是onLayout,此方法在View中是个空方法,什么都没有写。可实际上layout的方法在View中并没有被标识为final,这就意味是可以被重写的。
  继续查看ViewGoup中的相关代码,果然layout被重写了并添加了final标识,同时onLayout被标识为抽象方法,所以继承了ViewGroup的类是,是不能重写layout方法的,并且要实现onLayout方法。从代码可以看出,虽然ViewGroup重写了layout,实际本质上还是调用了View的layout,然后通过调用onLayout方法最终完成布局定位的。

@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);


  在DecorView中,并没有发现onLayout方法,所以它使用的肯定是其父类FrameLayout中的,找到FrameLayout的源码,可以查看到onLayout方法,具体如下:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();

final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();

final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();

mForegroundBoundsChanged = true;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();

int childLeft;
int childTop;

int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}

final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}

switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}

child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}


  从方法中,我们可以看出在Framelayout中最终调用了layoutChildren方法,在该方法中根据测量结果和一些布局属性对容器中每一个View都调用了layout方法进行了布局。根据以上的代码分析,我们可以得出View布局定位的流程图如下。



  疑问咨询或技术交流,请加入官方QQ群:

(452379712)

作者:杰瑞教育

出处:http://blog.csdn.net/jerehedu/

本文版权归烟台杰瑞教育科技有限公司和CSDN共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: