MaterialDesign-LinearLayoutCompat探究及源碼分析

簡(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捞慌,隨后出現(xiàn)的幾起案子耀鸦,更是在濱河造成了極大的恐慌,老刑警劉巖啸澡,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揭糕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锻霎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)揪漩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)旋恼,“玉大人,你說(shuō)我怎么就攤上這事奄容”” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵昂勒,是天一觀的道長(zhǎng)蜀细。 經(jīng)常有香客問(wèn)我,道長(zhǎng)戈盈,這世上最難降的妖魔是什么奠衔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮塘娶,結(jié)果婚禮上归斤,老公的妹妹穿的比我還像新娘。我一直安慰自己刁岸,他們只是感情好脏里,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著虹曙,像睡著了一般迫横。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酝碳,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天矾踱,我揣著相機(jī)與錄音,去河邊找鬼击敌。 笑死介返,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播圣蝎,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刃宵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了徘公?” 一聲冷哼從身側(cè)響起牲证,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关面,沒(méi)想到半個(gè)月后坦袍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡等太,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年捂齐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缩抡。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奠宜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞻想,到底是詐尸還是另有隱情压真,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布蘑险,位于F島的核電站滴肿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏佃迄。R本人自食惡果不足惜泼差,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望和屎。 院中可真熱鬧拴驮,春花似錦、人聲如沸柴信。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)随常。三九已至潜沦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绪氛,已是汗流浹背唆鸡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枣察,地道東北人争占。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓燃逻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親臂痕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伯襟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容