android 自定义组合控件

概论:

  1. 说在前面的话
  2. 继承框架提供的布局
  3. 布局文件
  4. 构造方法
  5. 状态保存

1.说在前面的话

自定义组合控件还是蛮简单的,你不用measure,layout,draw,当然你想的话也可以在组合控件上做这些操作的,不过大多数时候是没有必要的。组合控件的自定义属性和自定义view完全一样,这里比较难的是状态保存,因为一个页面可以有很多个你自定义的组合控件,但是系统的状态保存是依赖viewID的,所以你不能让ViewGroup来管理你的控件状态,只能自己管理了。当然,你也可能不需要状态保存,那就很简单了。

下面说说需要经历的步骤吧:

  • 让自定义的组合控件继承框架提供的布局,如LinearLayout,RelativeLayout等。
  • 把你组合控件里面的子控件放到一个布局文件中,然后在组合控件的构造方法中加载进来。
  • 采用merge作为你布局文件的根,这样可以减少嵌套层次。
  • 接下来是比较难的状态保存。
  • 自定义属性,这个和自定义view一样。

2.继承框架提供的布局

1
2
3
4
5
public class CompoundControl
extends LinearLayout
implements android.view.View.OnClickListener
{
...

3.布局文件

样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/fromDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Enter From Date"
android:layout_weight="70"
/>
<Button
android:id="@+id/fromButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Go"
android:layout_weight="30"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/toDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Enter To Date"
android:layout_weight="70"
/>
<Button
android:id="@+id/toButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Go"
android:layout_weight="30"
/>
</LinearLayout>
</merge>

4.构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public DurationControl(Context context) {
super(context);
initialize(context);
}
public DurationControl(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray t = context.obtainStyledAttributes(attrs,
R.styleable.DurationComponent, 0, 0);
durationUnits = t.getInt(R.styleable.DurationComponent_durationUnits,
durationUnits);
t.recycle();
initialize(context);
}
public DurationControl(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private void initialize(Context context) {
LayoutInflater lif = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
lif.inflate(R.layout.duration_view_layout, this);
Button b = (Button) this.findViewById(R.id.fromButton);
b.setOnClickListener(this);
b = (Button) this.findViewById(R.id.toButton);
b.setOnClickListener(this);
this.setSaveEnabled(true);
}

5.状态保存

前面说过了,由于你控件可能会在一个页面出现多次,所以viewID会重复,这样系统就没办法帮你管理状态了。所以如果你要管理children的状态,就必须亲自动手了,而不是让children自己管理自己的状态。

要想管理children的状态,你必须要知道ViewGroup的四个状态管理的方法,他们是:

1
2
3
4
dispatchSaveInstanceState
dispatchFreezeSelfOnly
dispatchRestoreInstanceState
dispatchThawSelfOnly

A ViewGroup uses dispatchSaveInstanceState to first save its own state by calling super (view’s) dispatchSaveInstanceState, which in turn triggers onSaveInstanceState on itself and then calls the dispatchSaveInstanceState for each of its children. If the children are plain views and not ViewGroups, this will result in having their onSaveInstanceState called. Listing 2-12 presents the pseudo code for how these key methods are meshed together.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ViewGroup.dispatchSaveInstanceState() {
View.dispatchSaveInstanceState()
...ends up calling its own ViewGroup.onSaveInstanceState()
Children.dispatchSaveInstanceState()
...ends up calling children's onSaveInstanceState()
}
View.dispatchSaveInstanceState() {
onSaveInstanceState()
}
ViewGroup.dispatchFreezeSelfOnly() {
View.dispatchSaveInstanceState()
...ends up calling ViewGroup.onSaveInstanceState()
}

技巧在于dispatchFreezeSelfOnly,这个ViewGroup方法只是简单的保存了ViewGroup的状态,这同样也发生在对应的恢复dispatchThawSelfOnly上。所以你可以重写ViewGroup的方法来阻止children自己管理状态。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container)
{
//Don't call this so that children won't be explicitly saved
//super.dispatchSaveInstanceState(container);
//Call your self onsavedinstancestate
super.dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(
SparseArray<Parcelable> container)
{
//Don't call this so that children won't be explicitly saved
//super.dispatchRestoreInstanceState(container);
super.dispatchThawSelfOnly(container);
}

接下来就需要ViewGroup在onSaveInstanceStateonRestoreInstanceState中管理children的状态了,实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
// Don't call this so that children won't be explicitly saved
// super.dispatchSaveInstanceState(container);
// Call your self onsavedinstancestate
super.dispatchFreezeSelfOnly(container);
Log.d(tag, "in dispatchSaveInstanceState");
}
@Override
protected void dispatchRestoreInstanceState(
SparseArray<Parcelable> container) {
// Don't call this so that children won't be explicitly saved
// .super.dispatchRestoreInstanceState(container);
super.dispatchThawSelfOnly(container);
Log.d(tag, "in dispatchRestoreInstanceState");
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Log.d(tag, "in onRestoreInstanceState");
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
// it is our state
SavedState ss = (SavedState) state;
// Peel it and give the child to the super class
super.onRestoreInstanceState(ss.getSuperState());
// this.fromDate = ss.fromDate;
// this.toDate= ss.toDate;
this.setFromDate(ss.fromDate);
this.setToDate(ss.toDate);
}
@Override
protected Parcelable onSaveInstanceState() {
Log.d(tag, "in onSaveInstanceState");
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.fromDate = this.fromDate;
ss.toDate = this.toDate;
return ss;
}
/*
* ***************************************************************
* Saved State inner static class
* ***************************************************************
*/
public static class SavedState extends BaseSavedState {
// null values are allowed
private Calendar fromDate;
private Calendar toDate;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcelable superState, Calendar inFromDate, Calendar inToDate) {
super(superState);
fromDate = inFromDate;
toDate = inToDate;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
if (fromDate != null) {
out.writeLong(fromDate.getTimeInMillis());
} else {
out.writeLong(-1L);
}
if (fromDate != null) {
out.writeLong(toDate.getTimeInMillis());
} else {
out.writeLong(-1L);
}
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("fromDate:"
+ DurationControl.getDateString(fromDate));
sb.append("fromDate:" + DurationControl.getDateString(toDate));
return sb.toString();
}
@SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
// Read back the values
private SavedState(Parcel in) {
super(in);
// Read the from date
long lFromDate = in.readLong();
if (lFromDate == -1) {
fromDate = null;
} else {
fromDate = Calendar.getInstance();
fromDate.setTimeInMillis(lFromDate);
}
// Read the from date
long lToDate = in.readLong();
if (lFromDate == -1) {
toDate = null;
} else {
toDate = Calendar.getInstance();
toDate.setTimeInMillis(lToDate);
}
}
}// eof-state-class

其实这就相当于把整个组合控件当成一个View来管理