轉:http://blog.csdn.net/lmj623565791/article/details/42407923html
一、概述
話說,隨着Android SDK版本的升級,不少控件增長了新的屬性方便咱們的使用,好比LinearLayout中多了:divider、showDividers等,用於爲其內部元素添加分隔;可是呢,這樣的屬性在較低版本的SDK中不能被支持,那麼,咱們在開發過程當中,可能會出現這樣的需求:將這個新的特性想辦法作到儘量的向下兼容。有人說,能夠本身寫個新的控件去實現,這樣的確能夠,可是會不會太霸氣了點。難道就沒有接地氣一點的方式麼?嗯,本文就是這樣的一個目的,以一種較爲接地氣的方式,實現新的屬性的向下兼容。java
這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!android
二、divider相關用法
爲了保證簡介性,這裏就不討論divider有多麼多麼好用神馬的,由於不是咱們的重點。固然了這裏提供一篇divider的參考:grid-spacing-on-android (基本就是引出divider的用處,有興趣的看下,本文的demo樣子也將參考本連接)。canvas
你們先看一個效果圖:數組

若是要實現,這樣的效果圖,對於這3個Button你們會怎麼作(主要看button):app
簡單嘛:一個水平的線性佈局,內部三個Button的weight都爲1,而後第二個Button設置leftMargin,rightMargin就能夠了。ide
嗯,沒問題,假設如今我有一個需求:通過某個操做Button3隱藏,而後讓Button1和Button2按以下佈局:佈局

這樣的感受是否是不錯,雖然少了一個,徹底不影響美觀;可是,若是按照上述的答案學習
「一個水平的線性佈局,內部三個Button的weight都爲1,而後第二個Button設置leftMargin,rightMargin就能夠了」 Button2的右邊會多出一個rightMargin 。 測試
因此,這樣的製做方式很明顯不是最優秀的,最優秀的方案是,使用Linearlayout的divider、showDividers屬性:
佈局代碼以下:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="20dp"
- android:layout_margin="10dp"
- android:background="#22444444"
- android:orientation="vertical" >
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="128dp"
- android:background="@android:color/darker_gray"
- android:gravity="center"
- android:text="application_logo" />
-
- <LinearLayout
- android:id="@+id/buttons_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:divider="@drawable/divider"
- android:orientation="horizontal"
- android:showDividers="middle" >
-
- <Button
- android:id="@+id/btn_first"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#ff0000"
- android:text="button_1" />
-
- <Button
- android:id="@+id/btn_second"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#00ff00"
- android:text="button_2" />
-
- <Button
- android:id="@+id/btn_third"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#0000ff"
- android:text="button_3" />
- </LinearLayout>
-
- </LinearLayout>
其實核心就是放置Button的LinearLayout設置了 android:divider="@drawable/divider"和 android:showDividers="middle" ;
固然了,有人會說,我就是任性,我就用margin來實現,消失的時候,我顯示去控制button的rightMargin爲0也能夠。嗯,是的,你不嫌麻煩的確沒問題。那麼如今問題又來了,我如今要求每一個Button間的間隔是藍色的,你怎麼辦?注意:咱們這裏的divider的值設置的是一個drawable噢~~沒轍了吧。
本例的drawable(divider.xml):
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <size android:width="15dp" />
- <solid android:color="@android:color/transparent" />
- </shape>
下面簡單介紹下divider、showDividers、dividerPadding:
divider能夠設置一個drawable做爲元素間的間隔;
showDividers:可取值爲:middle(子元素間)、beginning(第一個元素左邊)、end(最後一個元素右邊)、none;【關於垂直方向的相似】
dividerPadding:設置繪製間隔元素的上下padding。
很簡單,你們本身動手作下實驗就知道了。
好了,到此,咱們簡單介紹了divider等的好處以及使用方式。可是這麼優雅的來實現元素間的間隔只有在3.0以上才被支持,那麼3.0如下怎麼辦呢?
別怕,下面開始本文的重點,讓divider兼容至3.0一下。
三、自定義LinearLayout
看了標題,你們認爲又是自定義LinearLayout麼~~
嗯,繼承LinearLayout是確定的,咱們沒有辦法改變它的源碼,可是能夠經過繼承去改變一些特性。
注意下:如今的目的是兼容至3.0如下:
首先看一個3.0如下的效果圖,否則你說我騙你:

上面的佈局文件在3.0如下顯示就是這麼個樣子,徹底無視間隔。
首先考慮一個問題,對於divider、showDividers 3.0如下的LinearLayout確定無視呀,咋辦呢?
咱們實現個LinearLayout的子類,讓它認識divider和showDividers~~~重視一下這裏,這裏就是咱們向前邁進的一大步,之後遇到相似問題,都這麼幹。
一、識別高版本的屬性
- public class IcsLinearLayout extends LinearLayout
- {
- private static final int[] LL = new int[]
- {
- android.R.attr.divider,
- android.R.attr.showDividers,
- android.R.attr.dividerPadding
- };
-
- private static final int LL_DIVIDER = 0;
- private static final int LL_SHOW_DIVIDER = 1;
- private static final int LL_DIVIDER_PADDING = 2;
-
-
- private Drawable mDivider;
-
- private int mShowDividers;
-
- private int mDividerPadding;
-
- private int mDividerWidth;
- private int mDividerHeight;
-
- public IcsLinearLayout(Context context, AttributeSet attrs)
- {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, LL);
- setDividerDrawable(a.getDrawable(IcsLinearLayout.LL_DIVIDER));
- mDividerPadding = a.getDimensionPixelSize(LL_DIVIDER_PADDING, 0);
- mShowDividers = a.getInteger(LL_SHOW_DIVIDER, SHOW_DIVIDER_NONE);
- a.recycle();
- }
-
-
- 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();
- }
這裏貼出了成員變量和咱們的構造方法,成員變量中包含了3個屬性對應的接收變量;而後咱們在構造裏面對這三個屬性進行了獲取並賦值給相應的屬性;
這裏你們確定會困惑,我上面定義了一個整型數組,而後幾個變量爲數組下標,最後利用這個數組和下標在構造裏面獲取了值。是否是要問,你爲何這麼寫,你咋知道的?
嗯,這樣,你們隨便下載我以前包含自定義屬性的文章,或者你本身寫的:
這裏我拿了Android BitmapShader 實戰 實現圓形、圓角圖片這個例子中的源代碼,你們就不用下載了,看看我下面就明白了,我在這裏例子中自定義了兩個屬性:type和border_radius,看看咱們的R.java裏面生成了什麼樣的代碼:
- public static final int border_radius=0x7f010001;
- public static final int type=0x7f010000;
-
- public static final int[] RoundImageViewByShader = {
- 0x7f010000, 0x7f010001
- };
- public static final int RoundImageViewByShader_type = 0;
- public static final int RoundImageViewByShader_border_radius = 1;
看見木有,整型數組,下標;咱們的android.R.attr.xxx對應於上面的常量。是否是和咱們上例定義的如出一轍~~
對,自定義屬性怎麼獲取的,你照着模仿就是,無非如今的屬性是android.R.attr.xxx而不是你自定義的,本質沒區別。
好了,如今你們應該知道怎麼獲取高版本的屬性了~~
二、onMeasure
獲取到分隔元素之後,分隔元素確定有寬和高,咱們這裏把分隔元素的寬和高轉化爲合適的margin
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- {
-
- setChildrenDivider();
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- protected void setChildrenDivider()
- {
- final int count = getChildCount();
- for (int i = 0; i < count; i++)
- {
-
- View child = getChildAt(i);
-
- final int index = indexOfChild(child);
-
- final int orientation = getOrientation();
-
- final LayoutParams params = (LayoutParams) child.getLayoutParams();
-
- if (hasDividerBeforeChildAt(index))
- {
- if (orientation == VERTICAL)
- {
-
- params.topMargin = mDividerHeight;
- } else
- {
-
- params.leftMargin = mDividerWidth;
- }
- }
- }
- }
-
- public boolean hasDividerBeforeChildAt(int childIndex)
- {
- if (childIndex == 0 || childIndex == getChildCount())
- {
- return false;
- }
- 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;
- }
onMeasure中,將divider的寬和高,根據mShowDividers的狀況,設置給了合適的View的margin;
其實就是,將divider須要佔據的地方,利用margin空出來,咱們最後會在這個空的區域進行繪製divider,別忘了,咱們的divider是個drawable。
三、onDraw
好了,既然已經經過margin把須要繪製的地方空出來了,那麼下面就是繪製了~~~
- @Override
- protected void onDraw(Canvas canvas)
- {
-
- if (mDivider != null)
- {
- if (getOrientation() == VERTICAL)
- {
-
- drawDividersVertical(canvas);
- } else
- {
-
- drawDividersHorizontal(canvas);
- }
- }
- super.onDraw(canvas);
- }
-
-
- private void drawDividersHorizontal(Canvas canvas)
- {
- final int count = getChildCount();
-
- for (int i = 0; i < count; i++)
- {
- final View child = getChildAt(i);
-
- if (child != null && child.getVisibility() != GONE)
- {
-
- if (hasDividerBeforeChildAt(i))
- {
- final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child
- .getLayoutParams();
-
- final int left = child.getLeft() - lp.leftMargin
-
- drawVerticalDivider(canvas, left);
- }
- }
- }
- }
-
-
- public void drawVerticalDivider(Canvas canvas, int left)
- {
-
- mDivider.setBounds(left, getPaddingTop() + mDividerPadding, left
- + mDividerWidth, getHeight() - getPaddingBottom()
- - mDividerPadding);
-
- mDivider.draw(canvas);
- }
爲了代碼的簡短以及幫助你們的理解,這裏沒有貼出垂直方向的,水平方向的整個流程是完整的 。後面會貼出來垂直方向的繪製代碼。
其實也比較簡單,在onDraw裏面判斷方向,這裏以水平爲例:遍歷全部的子View,若是發現須要在其前繪製divider的,則算出divider的開始的位置(child.getLeft() - lp.leftMargin),而後調用drawVerticalDivider(),設置divider範圍,緊接着繪製出來。
垂直方向同理,就不贅述了,貼上代碼:
- private void drawDividersVertical(Canvas canvas)
- {
- final int count = getChildCount();
- for (int i = 0; i < count; i++)
- {
- final View child = getChildAt(i);
-
- if (child != null && child.getVisibility() != GONE)
- {
- if (hasDividerBeforeChildAt(i))
- {
- final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child
- .getLayoutParams();
- final int top = child.getTop() - lp.topMargin
- drawHorizontalDivider(canvas, top);
- }
- }
- }
- }
- private void drawHorizontalDivider(Canvas canvas, int top)
- {
- mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, getWidth()
- - getPaddingRight() - mDividerPadding, top + mDividerHeight);
- mDivider.draw(canvas);
- }
代碼說完了,下面幹嗎呢?固然是測試了~~不測試怎麼知道結果~~
四、測試
首先咱們把佈局文件中包含Button的Linelayout換成咱們的com.zhy.view.IcsLinearLayout
在3.0如下機子上運行:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="20dp"
- android:layout_margin="10dp"
- android:background="#22444444"
- android:orientation="vertical" >
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="128dp"
- android:background="@android:color/darker_gray"
- android:gravity="center"
- android:text="application_logo" />
-
- <com.zhy.view.IcsLinearLayout
- android:id="@+id/buttons_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:divider="@drawable/divider"
- android:orientation="horizontal"
- android:showDividers="middle" >
-
- <Button
- android:id="@+id/btn_first"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#ff0000"
- android:text="button_1" />
-
- <Button
- android:id="@+id/btn_second"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#00ff00"
- android:text="button_2" />
-
- <Button
- android:id="@+id/btn_third"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="#0000ff"
- android:text="button_3" />
- </com.zhy.view.IcsLinearLayout>
-
- </LinearLayout>
效果圖:

久違了~~咱們的分隔~~能夠看到在3.0如下機器完美實現~~~
but,別高興太早,咱們這麼改,3.0以上機器是什麼樣子呢?

哈哈,是否是完美實現了間隔~~~
如今能夠高興了~~~
你們如今確定有困惑,我擦,你在構造裏面獲取divider,而後在onDraw裏面本身繪製了divider,你們都知道3.0以上是支持的呀,確定也會繪製呀,你說沒衝突誰信呀~~~!!!
五、答疑
一、爲何和3.0以上沒有發生一些該有的衝突?
嗯,是的,3.0以上是支持的,爲何咱們在onDraw裏面本身繪製,而後調用super.onDraw居然沒有發生什麼衝突?
緣由很簡單:咱們看4.4LinearLayout的源碼:
- @Override
- protected void onDraw(Canvas canvas) {
- if (mDivider == null) {
- return;
- }
其實,源碼中也是在onDraw裏面去繪製divider,可是若是mDivider爲null,就會return。之因此沒有衝突,是由於咱們前面的某個操做讓其mDivider成員變量爲null了~~
如今去LinearLayout的構造方法:
- public LinearLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- ...
- setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));
- ...
- }
- 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爲其mDivider賦值,關鍵來了~~~~咱們的自定義的LinearLayout複寫了這個方法,也就是說,setDividerDrawable會調用子類的方法,這個父類的setDividerDrawable根本不會調用,從而致使mDivider爲null了~~~
爲null就對應了onDraw裏面的繪製~~ok~解答完畢。
二、這篇博客怎麼想到的?你咋知道代碼這麼寫?
我相信這樣的問題,不少人感興趣,其實也算巧合,以前知道有divider這個屬性;而後前段時間寫Android 教你打造炫酷的ViewPagerIndicator 不只僅是高仿MIUI 這篇博客的時候,特地去看了ViewPagerIndicator那個開源項目源碼,發現了一個IcsLinearLayout這樣的一個類,相似咱們上面實現的,固然了,我作了必定的修改;因而乎,仔細研究了這了類,以爲頗有必要寫成博客,達到文章開頭所敘述的的目的~其實你們有心的話,根據咱們上述的代碼,去看看LinearLayout源碼中如何去繪製divider,你會發現代碼基本是同樣的(ps:你沒發現問題1中的LinearLayout源碼的setDividerDrawable和咱們寫的如出一轍麼~);
好了,到此整篇文章就結束了,仍是那句話:」這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!「 不要偷懶花點時間去敲一敲,看一看,想想,你會發現裏面還藏着不少東西,別怕浪費時間,我研究和寫這篇博客的時間絕對超出你所學習這篇博客的時間~~
最後,歐來來~~
源碼點擊下載