LinearLayoutCompat源码简单分析

来源:互联网 发布:平面设计app软件 编辑:程序博客网 时间:2024/05/17 07:12
前言:伟大的foune说过,不看源码的安卓工程师不是一个优秀的程序猿。     今天,我将开始试水第一篇源码分析的博客,感谢各位那么帅、那么美,还来看我的博客。     目前水平有限,只是简单分析,大家参考着看看,如有纰漏,敬请指出。

一、应用示例

示例图

<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"android:padding="20dp"app:divider="@drawable/abc_list_divider_mtrl_alpha"app:showDividers="beginning|middle|end"><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:gravity="center"    android:text="LinearLayoutCompat" /><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:gravity="center"    android:text="LinearLayoutCompat" /><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:gravity="center"    android:text="LinearLayoutCompat" /><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:gravity="center"    android:text="made by foune" /></android.support.v7.widget.LinearLayoutCompat>

xml文件很简单,只需将我们常用的LinearLayout换成LinearLayoutCompat,并且设置divider和showDividers两个属性,即可完成对子控件添加分割线。
divider:设置分割线的drawable图片
showDividers:设置分割线的展示位置,如示例图所示,beginning显示在第一个子控件的顶部,middle显示在子控件两两之间,end显示在最后一个子控件的底部。
附加说明:默认orientation为horizontal,分割线为竖直方向。

二、源码分析

public class LinearLayoutCompat extends ViewGroup
LinearLayoutCompat继承自ViewGroup,如果之前了解过自定义ViewGroup,那么我们就知道主要涉及onMeasure(测量自身和内部的所有子控件),onLayout(摆放内部所有的子控件),onDraw(绘制)这三个方法。
下面我们来一步步分析下近2000行代码中的关键方法。
注:以下代码均以orientaion为vertical举例,horizontal代码类似。

1.构造方法

 public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,            R.styleable.LinearLayoutCompat, defStyleAttr, 0);    。。。。。。//此处省略    setDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider));    mShowDividers = a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE);    mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0);    a.recycle();}

构造方法中主要关注setDiveiderDrawable(Drawable divider)这个方法,请往下看。

2.设置分割线图片

public void setDividerDrawable(Drawable divider) {    if (divider == mDivider) {        return;    }    mDivider = divider;    if (divider != null) {        mDividerWidth = divider.getIntrinsicWidth();        mDividerHeight = divider.getIntrinsicHeight();    } else {        mDividerWidth = 0;        mDividerHeight = 0;    }    setWillNotDraw(divider == null);    requestLayout();  //重新测量摆放}

通过setDividerDrawable()方法我们可以设置分割线的图片,与xml中设置divider属性效果一致,不同的是通过java代码我们可以动态的设置分割线的图片。同理,setShowDividers()方法设置分割线的展现位置,与showDividers属性一致。setDividerPadding()类似。

3.onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    if (mOrientation == VERTICAL) {        measureVertical(widthMeasureSpec, heightMeasureSpec);    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}

见名知意,measureVertical()是orientation为vertical时的测量方法,这个方法代码较长,这里就不贴了,有兴趣的可以自行查看源码。我们不需要去详细理解其中的每一行代码,我们只要知道它通过遍历所有子控件测量每个子控件的width和height,我们设置的权重weight和边距padding等属性均参与了计算。

4.onLayout()

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);    }}void layoutVertical(int left, int top, int right, int bottom) {    final int paddingLeft = getPaddingLeft();    int childTop;    int childLeft;    // Where right end of child should go    final int width = right - left;    int childRight = width - getPaddingRight();    // Space available for child    int childSpace = width - paddingLeft - getPaddingRight();    final int count = getVirtualChildCount();    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;    final int minorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;    switch (majorGravity) {        case Gravity.BOTTOM:            // mTotalLength contains the padding already            childTop = getPaddingTop() + bottom - top - mTotalLength;            break;        // mTotalLength contains the padding already        case Gravity.CENTER_VERTICAL:            childTop = getPaddingTop() + (bottom - top - mTotalLength) / 2;            break;        case Gravity.TOP:        default:            childTop = getPaddingTop();            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 LinearLayoutCompat.LayoutParams lp =                    (LinearLayoutCompat.LayoutParams) child.getLayoutParams();            int gravity = lp.gravity;            if (gravity < 0) {                gravity = minorGravity;            }            final int layoutDirection = ViewCompat.getLayoutDirection(this);            final int absoluteGravity = GravityCompat.getAbsoluteGravity(gravity,                    layoutDirection);            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {                case Gravity.CENTER_HORIZONTAL:                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)                            + lp.leftMargin - lp.rightMargin;                    break;                case Gravity.RIGHT:                    childLeft = childRight - childWidth - lp.rightMargin;                    break;                case Gravity.LEFT:                default:                    childLeft = paddingLeft + lp.leftMargin;                    break;            }            if (hasDividerBeforeChildAt(i)) {                childTop += mDividerHeight;            }            childTop += lp.topMargin;            setChildFrame(child, childLeft, childTop + getLocationOffset(child),                    childWidth, childHeight);            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);            i += getChildrenSkipCount(child, i);        }    }}

同样的,layoutVertical()是orientation为vertical时摆放子控件的方法,并且权重和边距参与计算。

5.onDraw()

protected void onDraw(Canvas canvas) {    if (mDivider == null) {        return;    }    if (mOrientation == VERTICAL) {        drawDividersVertical(canvas);    } else {        drawDividersHorizontal(canvas);    }}

只有当我们设置了divider属性或者通过java代码setDividerDrawable(),才会绘制分割线;当orientation为vertical时,调用drawDividersVertical()方法中 的drawHorizontalDivider()方法绘制水平方向的分割线。

void drawDividersVertical(Canvas canvas) {    final int count = getVirtualChildCount();    for (int i = 0; i < count; i++) {        final View child = getVirtualChildAt(i);        if (child != null && child.getVisibility() != GONE) {            if (hasDividerBeforeChildAt(i)) {                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                final int top = child.getTop() - lp.topMargin - mDividerHeight;                drawHorizontalDivider(canvas, top);            }        }    }    if (hasDividerBeforeChildAt(count)) {        final View child = getVirtualChildAt(count - 1);        int bottom = 0;        if (child == null) {            bottom = getHeight() - getPaddingBottom() - mDividerHeight;        } else {            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            bottom = child.getBottom() + lp.bottomMargin;        }        drawHorizontalDivider(canvas, bottom);    }}

drawDividersVertical()绘制先遍历子控件并通过hasDividerBeforeChildAt()方法判断是否需要绘制分割线。

protected boolean hasDividerBeforeChildAt(int childIndex) {    if (childIndex == 0) {        return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;    } else if (childIndex == getChildCount()) {        return (mShowDividers & SHOW_DIVIDER_END) != 0;    } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {        boolean hasVisibleViewBefore = false;        for (int i = childIndex - 1; i >= 0; i--) {            if (getChildAt(i).getVisibility() != GONE) {                hasVisibleViewBefore = true;                break;            }        }        return hasVisibleViewBefore;    }    return false;}

这里的mShowDividers属性就是我们在xml中设置的showDividers属性或是java代码setShowDividers()方法设置的”beginning|middle|end”三种属性。
如果hasDividerBeforeChildAt()方法结果为true,那么就调用drawHorizontalDivider()方法绘制水平的分割线。

void drawHorizontalDivider(Canvas canvas, int top) {    mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,            getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);    mDivider.draw(canvas);}

三、总结

本篇讲解的是google官方的自定义VieGroup,主要关注onMeasure()、onLayout()及onDraw()三个方法,后面一段时间的博客我将以自定义View和ViewGroup为主题,有机会,我会写一些好看好玩儿的自定义View,欢迎各位帅哥美女前来围观。

0 0
原创粉丝点击