3

Android:都是Layout的BaselineAligned惹的祸

 2 years ago
source link: http://www.androidchina.net/3568.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android:都是Layout的BaselineAligned惹的祸 – Android开发中文站

你的位置:Android开发中文站 > Android开发 > 开发进阶 > Android:都是Layout的BaselineAligned惹的祸

此问题来自一个网友的提问http://ask.csdn.net/questions/206909#answer_140060

看下面的布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:layout_width="150dp"
android:layout_height="60dp"
android:background="#8f8f8f"
android:text="第一个" />
<TextView
android:layout_width="100dp"
android:layout_height="60dp"
android:background="#8f00"
android:gravity="center"
android:text="第二个"
android:textSize="20dp" />
</LinearLayout>

这个布局非常简单,LinearLayout里面嵌套了两个TextView组件,他们的高度一样,宽度不一样,期望结果是,两个TextView顶端是对齐的,高度一样,大小,文字对齐方式可以任意设置。这个布局实际渲染出来的结构是这样

Center

完全错位了,只要你设置不同的字号,Gravity,都会引起位置错乱,究其原因,就是LinearLayout的BaselineAligned属性惹的祸,看一下LinearLayout的默认属性

Center

很多时候,我们都被那个绿色框框给迷惑了,对于boolean属性,这个状态不一定是false,它只代表与系统设定的默认值一致,那么系统对这个变量是怎样设置的呢,看看LinearLayout源码(位置:\sdks\sources\android-19\android\widget

/**
* Whether the children of this layout are baseline aligned.  Only applicable
* if {@link #mOrientation} is horizontal.
*/
@ViewDebug.ExportedProperty(category = "layout")
private boolean mBaselineAligned = true;

看到了吧,默认值就是true,而且告诉我们只对水平布局有效。那么这个属性对布局产生了怎样的影响呢,继续看LinearLayout布局时都干了啥:

@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);
}
}

横向布局时,会调用layoutHorizontal,下面摘取一段代码

if (child == null) {
childLeft += measureNullChild(childIndex);
else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
int childBaseline = -1;
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
childTop = paddingTop + lp.topMargin;
if (childBaseline != -1) {
childTop += maxAscent[INDEX_TOP] - childBaseline;
}
break;
case Gravity.CENTER_VERTICAL:
// Removed support for baseline alignment when layout_gravity or
// gravity == center_vertical. See bug #1038483.
// Keep the code around if we need to re-enable this feature
// if (childBaseline != -1) {
//     // Align baselines vertically only if the child is smaller than us
//     if (childSpace - childHeight > 0) {
//         childTop = paddingTop + (childSpace / 2) - childBaseline;
//     } else {
//         childTop = paddingTop + (childSpace - childHeight) / 2;
//     }
// } else {
childTop = paddingTop + ((childSpace - childHeight) / 2) + lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = childBottom - childHeight - lp.bottomMargin;
if (childBaseline != -1) {
int descent = child.getMeasuredHeight() - childBaseline;
childTop -= (maxDescent[INDEX_BOTTOM] - descent);
}
break;
default:
childTop = paddingTop;
break;
}
if (hasDividerBeforeChildAt(childIndex)) {
childLeft += mDividerWidth;
}
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop, childWidth, childHeight);
childLeft += childWidth + lp.rightMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, childIndex);
}

我们看到在else…if代码段里,出现了baselineAligned的身影:

if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT)

如果是基线对齐,并且布局参数的高度设定的不是match_parent,那么就要获取chile的BaseLine,这里也就是TextView的BaseLine的值。经过一些列的计算,最后通过

setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);

对child进行OnLayout布局

private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}

再来看看TextView的getBaseline是咋样的

@Override
public int getBaseline() {
if (mLayout == null) {
return super.getBaseline();
}
int voffset = 0;
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
voffset = getVerticalOffset(true);
}
if (isLayoutModeOptical(mParent)) {
voffset -= getOpticalInsets().top;
}
return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
}

如果不是Gravity.TOP,就要通过getVerticalOffset计算voffset值了

int getVerticalOffset(boolean forceNormal) {
int voffset = 0;
final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
Layout l = mLayout;
if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
l = mHintLayout;
}
if (gravity != Gravity.TOP) {
int boxht = getBoxHeight(l);
int textht = l.getHeight();
if (textht < boxht) {
if (gravity == Gravity.BOTTOM)
voffset = boxht - textht;
else // (gravity == Gravity.CENTER_VERTICAL)
voffset = (boxht - textht) >> 1;
}
}
return voffset;
}

这个方法会计算一个垂直方向的偏移值,就是这个偏移值,直接影响了Layout布局中的childTop值,导致布局混乱,基线对齐,就是多个TextView的文字是保持对齐的,才不管你top和bottom。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK