您的位置:首页 > 移动开发 > Android开发

LinearLayoutCompat源码简单分析

2017-04-24 01:57 681 查看
前言:伟大的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,欢迎各位帅哥美女前来围观。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息