感觉FrameLayout很神秘,是因为用到它的地方少,一直觉得它鸡肋,原来是自己无知。最近需要实现一些layer的效果,就用到了它。它的用法很简单,这里就不多说了,这里就说说它的原理吧。
FrameLayout类里面没有什么东西,主要说的还是它的自身的布局参数FrameLayout.LayoutParams,布局参数类继承MarginLayoutParams。看名词就知道,就是控制view的外边距的,FrameLayout.LayoutParams本身自己定义的参数只有一个gravity。
好了,既然有了布局参数,那就会有空间的一些属性吧。首先FrameLayout也是一个View,所以他必然有Padding(view的内边距)相关属性。这里我们重点关注它的onMeasure与onLayout:
onMeasure意思就是计算出该ViewGroup的布局大小及孩子View的布局大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear();int maxHeight = 0;
int maxWidth = 0; int childState = 0;for (int i = 0; i < count; i++) {
final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } }// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and width
final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); }setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));count = mMatchParentChildren.size();
if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i);final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthMeasureSpec; int childHeightMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } if (lp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); }child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} } }简单的说父容器的width就是所有孩子View的width取最大值加View外边距和它的内边距:child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin+getPaddingLeftWithForeground() + getPaddingRightWithForeground();如果ViewGroup有图片,还有跟图片比较取较大值,容器的height也是同样的道理。FrameLayout孩子View的布局大小跟普通View大小确定是一样的。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
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 = getResolvedLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT: childLeft = parentLeft + lp.leftMargin; break; case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = parentRight - width - lp.rightMargin; break; 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);
} } }从代码中可以看出,孩子View左上角坐标的X值就是ViewGroup的左内边距加上view的外边距,Y值同理。
哎,怎么感觉自己写着写着,还把问题说复杂了点,感觉还是看代码能明白。
还有,我在2.2版本的代码上,gravity与margin属性必须同时设置,margin才能发挥作用,如果只有margin属性而没有设置gravity,就没有效果。如果有谁知道,欢迎指教哈。
在上一篇中说了下android里drag and drop 一个View,当时那个imageView放在一个LinearLayout里面,事实上放在FrameLayout同样适用,因为他们的布局参数都继承MarginLayoutParams类,所以通过margin控制他们的布局位置同样在FrameLayout使用。事实上GridLayout,LinearLayout,RelativeLayout,FrameLayout的布局参数都是继承MarginLayoutParams的。