Android:都是Layout的BaselineAligned惹的祸

来源:互联网 发布:网络运营商无服务 编辑:程序博客网 时间:2024/05/22 09:05

此问题来自一个网友的提问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。



1 0
原创粉丝点击