簡(jiǎn)述
谷歌Material Design推出了許多非常好用的兼容性控件,尤其是在appcompat-V7里面有很多為兼容而生的控件,這樣就可以做到高低版本和不同的ROM之間體驗(yàn)一致!還可以配合appcompat的主題使用達(dá)到體驗(yàn)一致性穗慕。例如:
1遵绰、android.support.v7.app.AlertDialog
2、進(jìn)度條樣式設(shè)置 style="@style/Widget.AppCompat.ProgressBar.Horizontal"
3巡社、SwipeRefreshLayout下拉刷新
4、PopupWindow手趣、ListPopupWindow晌该、PopupMenu、Button绿渣、EditText等等
5朝群、android.support.v7.widget.LinearLayoutCompat
這里主要來(lái)探究一下LinearLayoutCompat:
以前要在LinearLayout布局之間的子View之間添加分割線,還需要自己去自定義控件進(jìn)行添加或者就是在子View之間寫(xiě)很多個(gè)分割線View中符;LinearLayoutCompat的出現(xiàn)輕松解決了LinearLayout添加分割線的問(wèn)題姜胖,我們從以下兩個(gè)方面來(lái)對(duì)LinearLayoutCompat進(jìn)行介紹:
1、LinearLayoutCompat的使用
2淀散、?LinearLayoutCompat的源碼分析
LinearLayoutCompat的使用
LinearLayoutCompat位于support-v7包中右莱,LinearLayoutCompat其實(shí)就是LinerLayout組件的升級(jí)蚜锨,為了兼容低版本,使用前提:
1慢蜓、需要引入 compile 'com.android.support:appcompat-v7:26.1.0'
2亚再、使用LinearLayoutCompat需要自定義命名空間xmlns:app=”http://schemas.android.com/apk/res-auto”
這樣就可以使用如下LinearLayoutCompat特有的功能了:
app:divider=”@drawable/line”給分隔線設(shè)置自定義的drawable,這里你需要在drawable在定義shape資源晨抡,否則將沒(méi)有效果氛悬。
app:dividerPadding?給分隔線設(shè)置距離左右邊距的距離。
app:showDividers="beginning|middle|end"屬性耘柱。
beginning如捅,middle,end屬性值分別指明將在何處添加分割線帆谍。
beginning表示從該LinearLayoutCompat布局的最頂一個(gè)子view的頂部開(kāi)始伪朽。
middle表示在此LinearLayoutCompat布局內(nèi)的子view之間添加。
end表示在此LinearLayoutCompat最后一個(gè)子view的底部添加分割線汛蝙。
none表示不設(shè)置間隔線烈涮。
示例代碼:
? ? xmlns:app="http://schemas.android.com/apk/res-auto"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent"
? ? android:background="@color/white"
? ? android:orientation="vertical"
? ? app:showDividers="middle"
? ? app:divider="@drawable/line_diver_gray"
? ? app:dividerPadding="15dp">
? ? ? ? android:layout_width="match_parent"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:text="收藏"
? ? ? ? android:padding="15dp"/>
? ? ? ? android:layout_width="match_parent"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:text="相冊(cè)"
? ? ? ? android:padding="15dp"/>
? ? ? ? android:layout_width="match_parent"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:text="卡包"
? ? ? ? android:padding="15dp"/>
? ? ? ? android:layout_width="match_parent"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:text="設(shè)置"
? ? ? ? android:padding="15dp"/>
line_diver_gray.xml:
? ? android:shape="rectangle">
? ?
? ?
效果圖如下:
LinearLayoutCompat的源碼分析
看源碼需要有目的去看,分析實(shí)現(xiàn)的原理:LinearLayoutCompat是如何做到給里面的所有的child之間添加間隔線的窖剑?
觀看源碼坚洽,首先可以知道 LinearLayoutCompat繼承了ViewGroup,我們知道View的繪制會(huì)經(jīng)過(guò)三個(gè)方法:
1西土、onMearsue(測(cè)量自身和里面的所有子控件)
2讶舰、onLayout(擺放里面所有的子控件),
3、onDraw(繪制)
猜想:
1需了、mearsuredWidth,mearsuredHeight會(huì)變大(加上分割線)
2跳昼、擺放子控件位置會(huì)有一定的體現(xiàn)(childView: left/top/right/bottom)
3、onDraw繪制的時(shí)候也會(huì)有體現(xiàn)(childView: left/top/right/bottom)
1肋乍、首先我們查看它的構(gòu)造函數(shù):
1鹅颊、從構(gòu)造函數(shù)中,首先會(huì)把LinearLayoutCompat的所有風(fēng)格屬性的值保存到一個(gè)TintTypedArray數(shù)組中墓造,然后從中取出用戶給LinearLayoutCompat設(shè)置的orientation, gravity堪伍,baselineAligned的值,如果這些值存在觅闽,就給LinearLayoutCompat設(shè)置這些值帝雇。
2、當(dāng)然還會(huì)從TintTypedArray中取出weightSum蛉拙,baselineAlignedChildIndex尸闸,measureWithLargestChild等屬性
3、setDividerDrawable方法設(shè)置分割線的Drawable,非常明顯和分割線有關(guān)系
4室叉、接著是從TintTypedArray中繼續(xù)獲取mShowDividers和mDividerPadding的值睹栖,分別用于判斷顯示分割線的模式和分割線的Padding值為多少。
public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {
? ? super(context, attrs, defStyleAttr);
? ? final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
? ? ? ? ? ? R.styleable.LinearLayoutCompat, defStyleAttr, 0);
? ? int index = a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1);
? ? if (index >= 0) {
? ? ? ? setOrientation(index);
? ? }
? ? index = a.getInt(R.styleable.LinearLayoutCompat_android_gravity, -1);
? ? if (index >= 0) {
? ? ? ? setGravity(index);
? ? }
? ? boolean baselineAligned = a.getBoolean(R.styleable.LinearLayoutCompat_android_baselineAligned, true);
? ? if (!baselineAligned) {
? ? ? ? setBaselineAligned(baselineAligned);
? ? }
? ? mWeightSum = a.getFloat(R.styleable.LinearLayoutCompat_android_weightSum, -1.0f);
? ? mBaselineAlignedChildIndex =
? ? ? ? ? ? a.getInt(R.styleable.LinearLayoutCompat_android_baselineAlignedChildIndex, -1);
? ? mUseLargestChild = a.getBoolean(R.styleable.LinearLayoutCompat_measureWithLargestChild, false);
? ? 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();
}
我們查看setDividerDrawable方法的內(nèi)部實(shí)現(xiàn):
1茧痕、可以看到,該方法中傳進(jìn)來(lái)一個(gè)Drawable恼除,然后會(huì)進(jìn)行if判斷踪旷,是否和原有的Drawable相等,如果為true則return豁辉,不執(zhí)行下面的語(yǔ)句令野,如果不是,則將該Drawable設(shè)置給全局的mDivider徽级,
2气破、又是if判斷,如果傳進(jìn)來(lái)的divider!= null餐抢,則獲取它的固有寬高并設(shè)置給mDivider现使,否則mDivider的寬高設(shè)為0,然后會(huì)執(zhí)行setWillNotDraw和requestLayout方法
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();
}
2旷痕、查看分析View的繪制會(huì)經(jīng)過(guò)三個(gè)方法onMeasure碳锈、onLayout、onDraw
下面我們就查看一下這幾個(gè)方法的源碼進(jìn)行分析欺抗,看看分割線是如何進(jìn)行繪制的售碳。
1、首先查看一下onMeasure方法:
內(nèi)部就是根據(jù)Orientation的不同绞呈,調(diào)用不同的方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? if (mOrientation == VERTICAL) {
? ? ? ? measureVertical(widthMeasureSpec, heightMeasureSpec);
? ? } else {
? ? ? ? measureHorizontal(widthMeasureSpec, heightMeasureSpec);
? ? }
}
onMeasure分為了水平和豎直的情況贸人,我們這次以豎直情況為例分析。我們猜想可以知道佃声,在測(cè)量的時(shí)候艺智,肯定加了分隔線的高度(只看核心代碼):
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
//如果有分隔線,那么測(cè)量的時(shí)候就加上分割線的Drawable的高度
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
}
}
--measureVertical方法最后是通過(guò)setMeasuredDimension方法對(duì)測(cè)量的值進(jìn)行設(shè)置的秉溉;
--至于?maxWidth的值在源碼的前面有相應(yīng)的判斷進(jìn)行賦值力惯;
--所以整個(gè)measure的方法基本圍繞maxWidth和mTotalLength值的確定展開(kāi)的;
--其中如果hasDividerBeforeChildAt返回的值為true,mTotalLength會(huì)加上分割線的高度召嘶;
--最后通過(guò)setMeasuredDimension賦值父晶。
2、其次查看一下onLayout方法:
@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);
? ? }
}
看一下layoutVertical的邏輯弄跌,里面基本圍繞以下兩個(gè)值展開(kāi)的:
int childTop;
int childLeft;
循環(huán)遍歷子View甲喝,根據(jù)不同的gravity對(duì)childLeft和childTop進(jìn)行賦值,如果存在分割線childTop會(huì)加上分割線的高度mDividerHeight铛只,最后是通過(guò)setChildFrame方法進(jìn)行l(wèi)ayout的完成的
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);
? ? }
}
3埠胖、最后看一下onDraw方法:
onDraw方法內(nèi)部邏輯是糠溜,判斷mDivider是否為空,然后是根據(jù)mOrientation的屬性直撤,來(lái)調(diào)用不同的方法進(jìn)行橫或者豎的分割線繪制非竿。
@Override
protected void onDraw(Canvas canvas) {
? ? if (mDivider == null) {
? ? ? ? return;
? ? }
? ? if (mOrientation == VERTICAL) {
? ? ? ? drawDividersVertical(canvas);
? ? } else {
? ? ? ? drawDividersHorizontal(canvas);
? ? }
}
#查看drawDividersVertical方法內(nèi)部:
1、循環(huán)遍歷所有子孩子谋竖,進(jìn)行是否為空和是否為不可見(jiàn)的判斷红柱;
2、然后調(diào)用hasDividerBeforeChildAt(i)蓖乘,如果為true锤悄,則通過(guò)獲取child的LayoutParams進(jìn)行計(jì)算;
3嘉抒、然后就可以計(jì)算出分割線的top距離零聚;
4、然后調(diào)用drawHorizontalDivider(canvas,top)方法些侍。
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);
? ? }
}
#查看一下hasDividerBeforeChildAt方法的內(nèi)部邏輯:
1隶症、基本就是根據(jù)子孩子的位置進(jìn)行相應(yīng)的判斷,第一個(gè)位置娩梨,最后一個(gè)位置沿腰,還有中間所有位置,返回一個(gè)boolean值狈定;
2颂龙、會(huì)根據(jù)這個(gè)值來(lái)判斷是否畫(huà)分割線;
3纽什、然后回到drawDividersVertical方法中措嵌,它會(huì)在遍歷子View的;
4芦缰、最后調(diào)用drawHorizontalDivider方法企巢。
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;
}
#查看一下drawHorizontalDivider方法:
分割線是如何繪制上去的:
1、發(fā)現(xiàn)分割線其實(shí)是通過(guò)Drawable的setBounds方法進(jìn)行設(shè)置的让蕾,
2浪规、然后會(huì)調(diào)用Drawable的draw方法對(duì)分割線進(jìn)行繪制。
3探孝、drawDividersHorizontal方法的邏輯跟drawDividersVertical方法差不多笋婿,它最后調(diào)用的是drawVerticalDivider方法。
void drawHorizontalDivider(Canvas canvas, int top) {
? ? mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
? ? ? ? ? ? getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
? ? mDivider.draw(canvas);
}
為什么要看分割線繪制的源碼顿颅,因?yàn)樵诤芏嗫丶胁](méi)有分割線缸濒,我們可以通過(guò)學(xué)習(xí)谷歌的源碼,仿照著進(jìn)行分割線的繪制,比如recyclerView就沒(méi)有分割線庇配,但我們可以自己寫(xiě)一個(gè)分割線斩跌,對(duì)于recyclerView分割線設(shè)置
csdn地址:https://blog.csdn.net/hylxnq/article/details/80184112