通常會疑惑,當使用不同的布局方式時,子view得布局屬性就不太一樣,比如當父布局是LinearLayout時,子view就能有效的使用它的一些布局屬性如layout_weight、weightSum、等;當使用的是RelativeLayout時,其子view就能使用屬於它的有效的屬性;當然使用FrameLayout時,其屬性集就少多了,也就常說的FrameLayout是最簡單的布局方式了。
簡單的說我們就是想要實現自定的屬性可以再子view里面使用。
那么我們先來探索下ViewGroup中的LayoutParams吧。
viewGroup也是繼承自view,主要是實現布局容器。
那么它有個內部基類LayoutParams。當然,這個viewGroup中的LayoutParams是所有的FrameLayout、RelativeLayout、、、的LayoutParams的基礎類。當繼承后,子layoutParams就擁有了父親的所有的屬性集合
這個類注釋了,是用於view去告訴他們的父布局他們想咋滴,也就是說這個是子view和父布局的通訊方式吧
現在來看看ViewGroup中的addView的實現流程
public void addView(View child, int index) { ... LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); .... } addView(child, index, params); }
public void addView(View child, int index, LayoutParams params) {
...
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
}
通過整個addview的片段流程可以看到。
首先是checkLayoutParams,目的是檢測這個參數是否為空,如果為空的話就給它生成一個普通的LayoutParams;
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p != null; }
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
可以得到前面兩個方法protected主要是給子類去實現的,第三個方法是公開的。還是有些蹊蹺的。所以要實現布局參數轉換成自定義的參數,這三個方法就顯得尤為重要了。
最后把這個params賦予子view,那么params是誰實現的,子view中的mLayoutParams就是什么類型的。這就是為什么我們在代碼中改變view的LayoutParams時,把它轉換成其他類型了就會報錯的原因。
當然最為容易明白的還是先去研究下FrameLayout吧,因為這個類看下來不過300行代碼。它其中的FrameLayout.LayoutParams中自定義了一個Gravity屬性。
public static class LayoutParams extends MarginLayoutParams { public int gravity = -1; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout); gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, int gravity) { super(width, height); this.gravity = gravity; } .... public LayoutParams(LayoutParams source) { super(source); this.gravity = source.gravity; } }
並且也實現了add流程中所提的三個方法:
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FrameLayout.LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); }
最后FrameLayout才能在OnMeasure或者onLayout方法中對子view 進行大展身手:
研究完了原理后,我也實現了一下這個動作,也得到了一些對方法的使用猜測
代碼如下:
/** * 自定義測試viewgroup ,探究viewgroup中的addview的流程,已經子view中屬性的獲取 * Created by taofuxn on 2016/12/27. */ public class ViewPropertiesLayout extends ViewGroup { public ViewPropertiesLayout(Context context) { super(context); } public ViewPropertiesLayout(Context context, AttributeSet attrs) { super(context, attrs); } public ViewPropertiesLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected boolean checkLayoutParams(LayoutParams p) { boolean b = p instanceof LayoutParams; Log.i("AAA","checkLayoutParams "+b); return b; } //沒調用,當在xml布局中加載的時候不會調用這個方法 @Override protected LayoutParams generateLayoutParams(LayoutParams p) { Log.i("AAA","generateLayoutParams p"); return new MyLayoutParam(p); } //這個方法是通過傳入的屬性集合生成相應的LayouParams .在viewgroup中是找不到調用的地方,我估計是xml解析的時候調用這個方法並賦予屬性集合來生成params. //因為通過addview的流程和這里的打印log可以看到是先調用這個方法生成了params,再去checklayout方法,最后再把轉換的這個對象賦予給了子view的params @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { Log.i("AAA","generateLayoutParams attrs"); return new MyLayoutParam(getContext(),attrs); } //onmeasure,在viewgroup中是沒有對子view進行測量的處理的,所有在onlayout中是獲取不到子view的寬高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0 ; i < getChildCount() ; i++){ View child = getChildAt(i); child.layout(l,t,r,100); //將子view的布局參數強行轉換為本布局中自定義的LayoutParams,能這么做的原因是addview里面對子params進行了generate MyLayoutParam lp = (MyLayoutParam) child.getLayoutParams(); child.setBackgroundColor(lp.color); } } public class MyLayoutParam extends LayoutParams{ private int color ; public MyLayoutParam(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.custom); color = a.getColor(R.styleable.custom_layout_bg, Color.CYAN); a.recycle(); } public MyLayoutParam(int width, int height) { super(width, height); } public MyLayoutParam(MyLayoutParam source) { super(source); this.color = source.color ; } public MyLayoutParam(LayoutParams source){ super(source); } } }
1 <?xml version="1.0" encoding="utf-8"?> 2 <com.example.administrator.viewproperties.ViewPropertiesLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 android:id="@+id/activity_main" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 > 8 9 <TextView 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content" 12 app:layout_bg="@color/colorAccent" 13 android:textSize="20sp" 14 android:text="Hello World!" /> 15 </com.example.administrator.viewproperties.ViewPropertiesLayout>
這個布局只在xml中應用,所有會調用public 的generateLayoutParams(atters)方法來給子view生成自定義的布局參數MyLayoutParam.那么他的執行順序如下:
只有當子view的layoutparams不是MyLayoutParams的實例時才會調用,generateLayoutParma(p)這個方法生成一個屬於自定義布局屬性
這樣的話我的這自定義布局就有了自己布局參數。感覺比自定義view的自定義屬性高大上了很多。
我的探索可能有些錯誤。希望大神盛情指導