Android之Padding解析

相信大家都用过android里的padding(android:padding 或者 android:paddingLeft/Top/Right/Bottom),我们也都知道padding的作用是定义控件内容与控件边界的距离(margin是定义两个控件之间的距离)。也许大家都会用这一个属性了,但是也许不清楚这个属性是如何起作用的,这篇文章主要就是来解释这个问题。

比如,有这样一个布局:(注意:下面所有的分析都是基于orientation为vertical的LinearLayout基础上的

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/layout"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_gravity="center"
        android:background="#8285BF"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/image"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#FFFFAA"
            android:scaleType="fitXY"
            android:src="@drawable/ic_launcher" />

    </LinearLayout>

</LinearLayout>

下面三张图分别是设置id为R.id.layout的LinearLayout的paddingTop为0,200,-200的效果图:


    


当padding设置为正数的时候,我们一般容易理解,但是当设置为负数的时候,我们要怎样去理解呢?


我们先对这个现象做一个结论:

我们可以用通俗一些的比喻来描述padding。比如咱们的手机都比较大,但是显示屏幕并没有占满整个手机,它的周围会有一个边框,这个边框就是上面说的padding>0的效果,它减小了Child View的可用空间,这个时候手机就是我们的LinearLayout,手机屏幕就是我们的ImageView。而padding<0的效果,就像我们通过窗户看天空,虽然看到的只有一小片天空,但其实还有很大的天空没有被我们看到,这时候窗户就是我们的LinearLayout,而天空就是ImageView。

所以,虽然我们的LinearLayout只有300dp的高度,但是因为padding,改变了它的可以装载Child View的空间大小,当padding>0的时候,减小了这个装载空间的大小,当padding<0的时候,增大了这个装载空间的大小,虽然增大的这一部分我们没有看到,但它确实存在。

上面的结论其实可以用上面的三幅图来解释。我们需要知道LinearLayout的默认Gravity属性为Gravity.TOP,即放在它里面的Child View会从LinearLayout可用空间的顶部依次向下排列,第一幅图不用说,ImageView正好对齐LinearLayout的顶部,而第二幅图因为LinearLayout有一个正数的padding,所以减小了一部分空间,第三幅图中ImageView跑到上面去了,说明它去了上面才能和LinearLayout的顶部对齐,也就是说这个LinearLayout上面还有一部分我们看不见但真实存在的空间。



如果想要了解padding的具体工作流程,就必须去查看源码了,而如果只是想了解padding的效果,下面的内容就可以不用看了。


padding之所以造成上面的现象,主要是影响了View的布局过程,即layout过程。我们依然以LinearLayout为例(orientation=vertical):

LinearLayout中的onLayout()方法会根据当前的方向来调用不同的方法来进行layout,比如orientation=vertical时会调用下面的方法:

    void layoutVertical() {
        final int paddingLeft = mPaddingLeft;

        int childTop = mPaddingTop;
        int childLeft;
        
        // Where right end of child should go
        final int width = mRight - mLeft;
        int childRight = width - mPaddingRight;
        
        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;
        
        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;

        if (majorGravity != Gravity.TOP) {
           switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already, we add the top
                   // padding to compensate
                   childTop = mBottom - mTop + mPaddingTop - mTotalLength;
                   break;

               case Gravity.CENTER_VERTICAL:
                   childTop += ((mBottom - mTop)  - mTotalLength) / 2;
                   break;
           }
           
        }
        
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
                
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                
                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.LEFT:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;

                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;
                    default:
                        childLeft = paddingLeft;
                        break;
                }
                
                
                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

首先在第4行记录下了默认的childTop,这是一个关键的变量,代表的是第一个Child View在LinearLayout中的位置。为mPaddingTop,即我们给LinearLayout设置的paddingTop,16、17行是根据当前LinearLayout的方向来获得主方向上的gravity属性和次方向上的gravity属性,比如这里的orientation为vertical,所以我们的主方向的gravity属性就为垂直方向上的gravity属性。


19行用来判断当前主方向上的gravity,从这行就可以看到,默认的gravity就是Gravity.TOP,如果我们的gravity==Gravity.TOP,那childTop=mPaddingTop。如果我们改变了gravity属性,如果是Gravity.BOTTOM,进入21行,22行中改变了childTop的值,这个值是怎么计算出来的呢?我们首先需要知道当gravity为Gravity.BOTTOM的时候,Child View该怎么排列,显然,Child View中的最后一个View需要和LinearLayout的底部对齐。而默认情况下(即什么都不做的情况),我们的的第一个View是和LinearLayout的顶部对齐的,所以我们需要移动整个Child View以保证最后一个View和LinearLayout底部对齐,移动之后,显然childTop的值就变了。22行就是用来计算这个操作之后的childTop的值的。而如果我们想要明白这个操作,需要先明白在layout过程之前,已经进行了measure过程,即每个View的大小都已经获取到。在LinearLayout的onMeasure()中,用一个成员变量mTotalLength记录下了“所有Child View的高度和(包括了margin) + mPadddingTop + mPaddingBottom”,我们可以记录为mTotalLength = contentHeight + mPaddingTop + mPaddingBottom,contentHeight为所有ChildView的高度。所以22行可以看做是:mBottom - mTop + mPaddingTop - (contentHeight + mPaddingTop + mPaddingBottom),即mBottom - mTop - mPaddingBottom - contentHeight,所以,Gravity.BOTTOM的情况下,是没有考虑mPaddingTop的,而上面的(mBottom - mTop - mPaddingBottom)可以看作是"真实的可以装载Child View的空间",用它减去contentHeight,就代表了新的childTop,我们的Child View和这个控件的底部刚好对齐【如果想象Child View和LinearLayout的顶部对齐(不要考虑mPaddingTop),这个公式就好理解了】。28行的计算和上面类似,可以化为:((mBottom - mTop - mPaddingTop - mPaddingBottom) - contentHeight) / 2,其中,(mBottom - mTop - mPaddingTop - mPaddingBottom)就可以看作是我们"真正可以装载Child View的空间"。用它减去Child View的高度,得到一个装载空间和装载内容的差值,再除以2,得到的就是我们的Child View需要偏移的距离。


之所以花这么大的力气解释这个childTop,就是因为它决定了第一个Child View的位置,后面的Child View都以它为基础来进行排列。从69~72行中可以看到,前一个Child View布局完成后,会改变childTop的值,后面的Child View布局的时候会以新的childTop值为标准。总之,我们记住这个childTop值是一个代表位置的值就好了。







  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值