轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/42407923 ,本文出自:【張鴻洋的博客】
html
話說,隨着Android SDK版本的升級,不少控件增長了新的屬性方便咱們的使用,好比LinearLayout中多了:divider、showDividers等,用於爲其內部元素添加分隔;可是呢,這樣的屬性在較低版本的SDK中不能被支持,那麼,咱們在開發過程當中,可能會出現這樣的需求:將這個新的特性想辦法作到儘量的向下兼容。有人說,能夠本身寫個新的控件去實現,這樣的確能夠,可是會不會太霸氣了點。難道就沒有接地氣一點的方式麼?嗯,本文就是這樣的一個目的,以一種較爲接地氣的方式,實現新的屬性的向下兼容。java
這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!android
爲了保證簡介性,這裏就不討論divider有多麼多麼好用神馬的,由於不是咱們的重點。固然了這裏提供一篇divider的參考:grid-spacing-on-android (基本就是引出divider的用處,有興趣的看下,本文的demo樣子也將參考本連接)。canvas
你們先看一個效果圖:數組
若是要實現,這樣的效果圖,對於這3個Button你們會怎麼作(主要看button):微信
簡單嘛:一個水平的線性佈局,內部三個Button的weight都爲1,而後第二個Button設置leftMargin,rightMargin就能夠了。app
嗯,沒問題,假設如今我有一個需求:通過某個操做Button3隱藏,而後讓Button1和Button2按以下佈局:ide
這樣的感受是否是不錯,雖然少了一個,徹底不影響美觀;可是,若是按照上述的答案佈局
「一個水平的線性佈局,內部三個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>
固然了,有人會說,我就是任性,我就用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能夠設置一個drawable做爲元素間的間隔;
showDividers:可取值爲:middle(子元素間)、beginning(第一個元素左邊)、end(最後一個元素右邊)、none;【關於垂直方向的相似】
dividerPadding:設置繪製間隔元素的上下padding。
很簡單,你們本身動手作下實驗就知道了。
好了,到此,咱們簡單介紹了divider等的好處以及使用方式。可是這麼優雅的來實現元素間的間隔只有在3.0以上才被支持,那麼3.0如下怎麼辦呢?
別怕,下面開始本文的重點,讓divider兼容至3.0一下。
看了標題,你們認爲又是自定義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; /** * android:dividers */ private Drawable mDivider; /** * 對應:android:showDividers */ private int mShowDividers; /** * 對應:android:dividerPadding */ 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而不是你自定義的,本質沒區別。
好了,如今你們應該知道怎麼獲取高版本的屬性了~~
獲取到分隔元素之後,分隔元素確定有寬和高,咱們這裏把分隔元素的寬和高轉化爲合適的margin
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //將分隔元素的寬高轉化爲對應的margin setChildrenDivider(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 將分隔元素的寬高轉化爲對應的margin */ protected void setChildrenDivider() { final int count = getChildCount(); for (int i = 0; i < count; i++) { //遍歷每一個子View View child = getChildAt(i); //拿到索引 final int index = indexOfChild(child); //方向 final int orientation = getOrientation(); final LayoutParams params = (LayoutParams) child.getLayoutParams(); //判斷是否須要在子View左邊繪製分隔 if (hasDividerBeforeChildAt(index)) { if (orientation == VERTICAL) { //若是須要,則設置topMargin爲分隔元素的高度(垂直時) params.topMargin = mDividerHeight; } else { //若是須要,則設置leftMargin爲分隔元素的寬度(水平時) params.leftMargin = mDividerWidth; } } } } /** * 判斷是否須要在子View左邊繪製分隔 */ 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--) { //當前index的前一個元素不爲GONE則認爲須要 if (getChildAt(i).getVisibility() != GONE) { hasVisibleViewBefore = true; break; } } return hasVisibleViewBefore; } return false; }
其實就是,將divider須要佔據的地方,利用margin空出來,咱們最後會在這個空的區域進行繪製divider,別忘了,咱們的divider是個drawable。
好了,既然已經經過margin把須要繪製的地方空出來了,那麼下面就是繪製了~~~
@Override protected void onDraw(Canvas canvas) { if (mDivider != null) { if (getOrientation() == VERTICAL) { //繪製垂直方向的divider drawDividersVertical(canvas); } else { //繪製水平方向的divider drawDividersHorizontal(canvas); } } super.onDraw(canvas); } /** * 繪製水平方向的divider * @param canvas */ private void drawDividersHorizontal(Canvas canvas) { final int count = getChildCount(); //遍歷全部的子View for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child != null && child.getVisibility() != GONE) { //若是須要繪製divider if (hasDividerBeforeChildAt(i)) { final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); //獲得開始的位置,getLeft爲當前View的左側,而左側有margin,因此之差爲divider繪製的開始區域 final int left = child.getLeft() - lp.leftMargin/* * - * mDividerWidth */; //繪製divider drawVerticalDivider(canvas, left); } } } } /** * 繪製divider,根據left,水平方向繪製 * @param canvas * @param left */ public void drawVerticalDivider(Canvas canvas, int left) { //設置divider的範圍 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/* * - * mDividerHeight */; 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; }
如今去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(); }
爲null就對應了onDraw裏面的繪製~~ok~解答完畢。
二、這篇博客怎麼想到的?你咋知道代碼這麼寫?
我相信這樣的問題,不少人感興趣,其實也算巧合,以前知道有divider這個屬性;而後前段時間寫Android 教你打造炫酷的ViewPagerIndicator 不只僅是高仿MIUI 這篇博客的時候,特地去看了ViewPagerIndicator那個開源項目源碼,發現了一個IcsLinearLayout這樣的一個類,相似咱們上面實現的,固然了,我作了必定的修改;因而乎,仔細研究了這了類,以爲頗有必要寫成博客,達到文章開頭所敘述的的目的~其實你們有心的話,根據咱們上述的代碼,去看看LinearLayout源碼中如何去繪製divider,你會發現代碼基本是同樣的(ps:你沒發現問題1中的LinearLayout源碼的setDividerDrawable和咱們寫的如出一轍麼~);
好了,到此整篇文章就結束了,仍是那句話:」這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!「 不要偷懶花點時間去敲一敲,看一看,想想,你會發現裏面還藏着不少東西,別怕浪費時間,我研究和寫這篇博客的時間絕對超出你所學習這篇博客的時間~~
最後,歐來來~~
我建了一個QQ羣,方便你們交流。羣號:423372824
----------------------------------------------------------------------------------------------------------
博主部分視頻已經上線,若是你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):