最新消息:欢迎访问Android开发中文站!商务联系QQ:1304524325

Android:都是Layout的BaselineAligned惹的祸

开发进阶 AndroidChina 7070浏览 0评论

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

看下面的布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     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顶端是对齐的,高度一样,大小,文字对齐方式可以任意设置。这个布局实际渲染出来的结构是这样

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

很多时候,我们都被那个绿色框框给迷惑了,对于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。

转载请注明:Android开发中文站 » Android:都是Layout的BaselineAligned惹的祸

您必须 登录 才能发表评论!