假設有下面一個佈局文件php
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:layout_width="100dp" android:layout_height="100dp" android:text="Hello World!" />
</LinearLayout>
複製代碼
系統在加載這個佈局的時候,會建立一個LinearLayout
對象和一個TextView
對象,而後會調用LinearLayout.addView()
方法保存這個TextView
對象,同時也會建立LinearLayout.LayoutParams
對象來保存TextView
所聲明的佈局參數,例如layout_width
和layout_height
。這就是你能夠在LinearLayout
的子View中聲明layout_width
和layout_height
屬性的緣由。java
ViewGroup
有兩個最基本的佈局參數類,ViewGroup.LayoutParams
和ViewGroup.MarginLayoutParams
。android
全部的自定義ViewGroup
的佈局參數類必需要直接或者間接繼承自ViewGroup.LayoutParams
類,由於你須要支持layout_width
和layout_height
佈局屬性。app
若是你想要讓自定義ViewGroup
的佈局參數屬性支持margin
,例如layout_marginLeft
,那麼自定義ViewGroup
的佈局參數類須要直接或者間接繼承自ViewGroup.MarginLayoutParams
,由於它有解析margin
屬性的功能。ide
假設如今有一個自定義ViewGroup
,名字叫作CornerLayout
。CornerLayout
會根據子View的layou_corner
佈局屬性決定子View放置在哪一個角落。例如如今有下面一個佈局函數
<?xml version="1.0" encoding="utf-8"?>
<com.bxll.layoutparamsdemo.CornerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">
<View android:layout_width="100dp" android:layout_height="100dp" android:background="@color/colorAccent" app:layout_corner="leftBottom" />
</com.bxll.layoutparamsdemo.CornerLayout>
複製代碼
layout_corner
的值爲leftBottom
,所以這個View會顯示在CornerLayout
的左下角,以下圖所示佈局
首先須要爲CornerLayout
建立一個自定義的layout_corner
屬性測試
<declare-styleable name="CornerLayout_Layout">
<attr name="layout_corner" format="flags" >
<flag name="leftTop" value="0x01" />
<flag name="rightTop" value="0x02" />
<flag name="leftBottom" value="0x04" />
<flag name="rightBottom" value="0x08" />
</attr>
</declare-styleable>
複製代碼
能夠看出layout_corner
有四個屬性值,,名字分別爲leftTop
,rightTop
,leftBottom
,rightBottom
,而且這個四個名字有對應的十六進制值。this
定義了layout_corner
屬性後,就能夠在XML佈局參數中使用這個佈局屬性,不過前提是父View必須是CornerLayout
。spa
如今要給CornerLayout
建立一個佈局參數類,用來解析layout_corner
屬性,我把它命名爲CornerLayoutParams
。因爲我須要CornerLayoutParams
支持margin
特性,所以我選擇讓它繼承自ViewGroup.MarginLayoutParams
。
public class CornerLayout extends ViewGroup {
// 定義佈局參數類
public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
// 這些常量須要與layout_corner的屬性值相對應
public static final int CORNER_LEFT_TOP = 0x01;
public static final int CORNER_RIGHT_TOP = 0x02;
public static final int CORNER_LEFT_BOTTOM = 0x04;
public static final int CORNER_RIGHT_BOTTOM = 0x08;
public int mCorner = CORNER_LEFT_TOP;
public CornerLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CornerLayout_Layout);
mCorner = a.getInt(R.styleable.CornerLayout_Layout_layout_corner, CORNER_LEFT_TOP);
a.recycle();
}
}
}
複製代碼
這裏,我只建立了一個構造函數CornerLayoutParams(Context c, AttributeSet attrs)
,而且在這個構造函數中解析出layout_corner
屬性值。
其實還有還有不少構造函數,而這一個構造函數是必須的,由於系統在解析佈局文件的時候,會調用這個構造函數來建立佈局參數。
那麼問題來了,系統怎麼精確的知道是建立CornerLayout.CornerLayoutParams
對象而不是建立其餘的佈局參數類的對象,例如LinearLayout.LayoutParams
。固然是複寫某個系統的接口,這個接口就是ViewGroup
類的generateLayoutParams(AttributeSet attrs)
方法。
所以在CornerLayout
中複寫這個接口來建立CornerLayout.CornerLayoutParams
對象。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CornerLayoutParams(getContext(), attrs);
}
複製代碼
如此一來就能夠在XML佈局中快樂的使用layout_corner
佈局屬性了,並且也可使用margin
這一類屬性。例以下面一個佈局就使用了這兩個佈局屬性
<?xml version="1.0" encoding="utf-8"?>
<com.umx.layoutparamsdemo.CornerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<View android:layout_width="100dp" android:layout_height="100dp" android:layout_marginEnd="10dp" android:layout_marginBottom="10dp" android:background="@color/colorAccent" app:layout_corner="rightBottom" />
</com.umx.layoutparamsdemo.CornerLayout>
複製代碼
顯示效果以下
到目前爲止,咱們只實現了在XML佈局中爲CornerLayout
添加子View的功能,然而並無實現動態添加子View功能。
動態添加子View是由ViewGroup
的addView()
這一類方法實現的。根據是否帶有佈局參數類型的參數,能夠把addView()
分爲兩類,以下。
// 不帶有佈局參數類型參數的方法
public void addView(View child) {}
public void addView(View child, int index) {}
public void addView(View child, int width, int height) {}
// 帶有佈局參數類型參數的方法
public void addView(View child, LayoutParams params) {}
public void addView(View child, int index, LayoutParams params) {}
複製代碼
若是你使用的是不帶佈局參數類型參數的addView()
方法,系統須要建立一個佈局參數對象。以addView(View child, int index)
爲例來分析下
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// 獲取佈局參數對象
LayoutParams params = child.getLayoutParams();
if (params == null) {
// 若是不存在佈局參數對象,那麼就建立一個默認的
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
複製代碼
能夠看到,若是添加的子View沒有佈局參數對象,那麼就會調用generateDefaultLayoutParams()
方法來建立一個默認的佈局參數對象。
所以CornerLayout
若是須要支持動態添加View的特性,那麼還須要複寫generateDefaultLayoutParams()
方法,在這個方法中須要提供最基本的寬高屬性,以及mCorner
的默認值。
public class CornerLayout extends ViewGroup {
/** * 系統建立默認佈局參數的接口。 */
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
/** * 定義佈局參數類。 */
public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
public int mCorner = CORNER_LEFT_TOP;
public CornerLayoutParams(int width, int height) {
super(width, height);
// 因爲mCorner有默認值,所以這裏再也不提供默認值
}
}
}
複製代碼
無論addView()
方法是否帶有佈局參數類型的參數,最終都會調用addViewInner()
方法來實現,而addViewInner()
方法根據狀況來校訂佈局參數類型的對象
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) {
// ...
// 若是佈局參數對象不合法就須要校訂對象
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
// ...
}
複製代碼
checkLayoutParams()
方法用來檢測佈局參數類型的對象是否合法,對於CornerLayout
來講,須要檢測這個對象的類型是否爲CornerLayoutParams
,所以實現以下
/** * 檢測參數類型是否合法。 */
@Override
protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof CornerLayoutParams;
}
複製代碼
若是佈局參數對象不合法,那麼就須要調用generateLayoutParams(LayoutParams p)
來校訂,而校訂的方式是抽取須要的佈局屬性,例如layout_widht
, layout_height
,以及margin
。
那麼,CornerLayout
的generateLayoutParams(LayoutParams p)
的實現以下
public class CornerLayout extends ViewGroup {
/** * 根據不合法的參數p, 從新建立CornerLayoutParams對象。 */
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
if (p instanceof MarginLayoutParams) {
return new CornerLayoutParams((MarginLayoutParams)p);
}
return new CornerLayoutParams(p);
}
/** * 定義佈局參數。 */
public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
public int mCorner = CORNER_LEFT_TOP;
public CornerLayoutParams(MarginLayoutParams source) {
// 調用父類構造函數解析layout_width, layout_height, margin屬性
super(source);
// mCorner有默認值,所以這裏不須要再提供默認值
}
public CornerLayoutParams(LayoutParams source) {
// 調用父類構造函數,解析layout_width和layout_heigth屬性
super(source);
// mCorner有默認值,所以這裏不須要再提供默認值
}
}
}
複製代碼
至此,咱們已經爲CornerLayout
實現了動態添加子View的功能。
假設MainActivity.java
加載的佈局以下
<?xml version="1.0" encoding="utf-8"?>
<com.umx.layoutparamsdemo.CornerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/corner_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.umx.layoutparamsdemo.CornerLayout>
複製代碼
很簡單,就一個CornerLayout
佈局。如今咱們到代碼中動態建立一個View對象,並添加到CornerLayout
中
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CornerLayout cornerLayout = findViewById(R.id.corner_layout);
// 建立一個紅色背景的View對象
View view = new View(this);
view.setBackgroundColor(getResources().getColor(R.color.colorAccent));
// 建立佈局參數對象
CornerLayout.CornerLayoutParams layoutParams =
new CornerLayout.CornerLayoutParams(300, 300);
// 設置layout_corner值
layoutParams.mCorner = CornerLayout.CornerLayoutParams.CORNER_RIGHT_TOP;
// 設置margin值
layoutParams.rightMargin = 30;
layoutParams.topMargin = 40;
// 設置佈局參數
view.setLayoutParams(layoutParams);
// 添加到CornerLayout中
cornerLayout.addView(view);
}
}
複製代碼
代碼中建立了一個View對象,而且設置了layout_corner
以及margin
屬性,最後把這個View對象添加到CornerLayout
佈局中。實際效果就是View顯示在右上角,而且rightMargin
爲30px
,topMargin
爲40px
。
下面給出完整的CornerLayout
代碼,以供參考
public class CornerLayout extends ViewGroup {
public CornerLayout(Context context) {
this(context, null);
}
public CornerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
// 只對第一個子View進行layout
View first = getChildAt(0);
CornerLayoutParams layoutParams = (CornerLayoutParams) first.getLayoutParams();
int left;
int top;
switch (layoutParams.mCorner) {
case CornerLayoutParams.CORNER_LEFT_TOP:
left = getPaddingLeft() + layoutParams.leftMargin;
top = getPaddingTop() + layoutParams.topMargin;
break;
case CornerLayoutParams.CORNER_RIGHT_TOP:
top = getPaddingTop() + layoutParams.topMargin;
left = getWidth() - getPaddingRight() - first.getMeasuredWidth() - layoutParams.rightMargin;
break;
case CornerLayoutParams.CORNER_LEFT_BOTTOM:
top = getHeight() - getPaddingBottom() - first.getMeasuredHeight() - layoutParams.bottomMargin;
left = getPaddingLeft() + layoutParams.leftMargin;
break;
case CornerLayoutParams.CORNER_RIGHT_BOTTOM:
top = getHeight() - getPaddingBottom() - layoutParams.bottomMargin - first.getMeasuredHeight();
left = getWidth() - getPaddingRight() - layoutParams.rightMargin - first.getMeasuredWidth();
break;
default:
left = getPaddingLeft() + layoutParams.leftMargin;
top = getPaddingTop() + layoutParams.topMargin;
}
first.layout(left, top, left + first.getMeasuredWidth(), top + first.getMeasuredHeight());
}
}
/** * 系統建立佈局參數的接口 */
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CornerLayoutParams(getContext(), attrs);
}
/** * 系統建立默認佈局參數的接口。 */
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new CornerLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
/** * 檢測參數類型是否合法。 */
@Override
protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof CornerLayoutParams;
}
/** * 根據不合法的參數p, 從新建立CornerLayoutParams對象。 */
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
if (p instanceof MarginLayoutParams) {
return new CornerLayoutParams((MarginLayoutParams)p);
}
return new CornerLayoutParams(p);
}
/** * 定義佈局參數類。 */
public static class CornerLayoutParams extends ViewGroup.MarginLayoutParams {
public static final int CORNER_LEFT_TOP = 0x01;
public static final int CORNER_RIGHT_TOP = 0x02;
public static final int CORNER_LEFT_BOTTOM = 0x04;
public static final int CORNER_RIGHT_BOTTOM = 0x08;
public int mCorner = CORNER_LEFT_TOP;
public CornerLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CornerLayout_Layout);
mCorner = a.getInt(R.styleable.CornerLayout_Layout_layout_corner, CORNER_LEFT_TOP);
a.recycle();
}
public CornerLayoutParams(int width, int height) {
super(width, height);
}
public CornerLayoutParams(MarginLayoutParams source) {
super(source);
}
public CornerLayoutParams(LayoutParams source) {
super(source);
}
}
}
複製代碼