首先看效果图,
看下这两个界面,第一个中用到了一个自定义的FlowRadioGroup,支持复合子控件,自定义布局;
第二个界面中看到了输入的数字 自动4位分割了吧;也用到了自定义的DivisionEditText控件。
下面直接看源码FlowRadioGroup了;
1 /*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.newgame.sdk.view;
18
19 import java.util.ArrayList;
20
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.CompoundButton;
27 import android.widget.LinearLayout;
28 import android.widget.RadioButton;
29
30 /** 可以放多种布局控件,能找到radiobutton */
31 public class FlowRadioGroup extends LinearLayout {
32 // holds the checked id; the selection is empty by default
33 private int mCheckedId = -1;
34 // tracks children radio buttons checked state
35 private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
36 // when true, mOnCheckedChangeListener discards events
37 private boolean mProtectFromCheckedChange = false;
38 private OnCheckedChangeListener mOnCheckedChangeListener;
39 private PassThroughHierarchyChangeListener mPassThroughListener;
40
41 // 存放当前的radioButton
42 private ArrayList<RadioButton> radioButtons;
43
44 public FlowRadioGroup(Context context) {
45 super(context);
46 setOrientation(VERTICAL);
47 init();
48 }
49
50 public FlowRadioGroup(Context context, AttributeSet attrs) {
51 super(context, attrs);
52 init();
53 }
54
55 private void init() {
56 mChildOnCheckedChangeListener = new CheckedStateTracker();
57 mPassThroughListener = new PassThroughHierarchyChangeListener();
58 super.setOnHierarchyChangeListener(mPassThroughListener);
59 radioButtons = new ArrayList<RadioButton>();
60 }
61
62 @Override
63 public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
64 // the user listener is delegated to our pass-through listener
65 mPassThroughListener.mOnHierarchyChangeListener = listener;
66 }
67
68 @Override
69 protected void onFinishInflate() {
70 super.onFinishInflate();
71
72 // checks the appropriate radio button as requested in the XML file
73 if (mCheckedId != -1) {
74 mProtectFromCheckedChange = true;
75 setCheckedStateForView(mCheckedId, true);
76 mProtectFromCheckedChange = false;
77 setCheckedId(mCheckedId);
78 }
79 }
80
81 @Override
82 public void addView(View child, int index, ViewGroup.LayoutParams params) {
83 if (child instanceof RadioButton) {
84 final RadioButton button = (RadioButton) child;
85 radioButtons.add(button);
86
87 if (button.isChecked()) {
88 mProtectFromCheckedChange = true;
89 if (mCheckedId != -1) {
90 setCheckedStateForView(mCheckedId, false);
91 }
92 mProtectFromCheckedChange = false;
93 setCheckedId(button.getId());
94 }
95 } else if (child instanceof ViewGroup) {// 如果是复合控件
96 // 遍历复合控件
97 ViewGroup vg = ((ViewGroup) child);
98 setCheckedView(vg);
99 }
100
101 super.addView(child, index, params);
102 }
103
104 /** 查找复合控件并设置radiobutton */
105 private void setCheckedView(ViewGroup vg) {
106 int len = vg.getChildCount();
107 for (int i = 0; i < len; i++) {
108 if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态
109 final RadioButton button = (RadioButton) vg.getChildAt(i);
110 // 添加到容器
111 radioButtons.add(button);
112 if (button.isChecked()) {
113 mProtectFromCheckedChange = true;
114 if (mCheckedId != -1) {
115 setCheckedStateForView(mCheckedId, false);
116 }
117 mProtectFromCheckedChange = false;
118 setCheckedId(button.getId());
119 }
120 } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置
121 ViewGroup childVg = (ViewGroup) vg.getChildAt(i);
122 setCheckedView(childVg);
123 }
124 }
125 }
126
127 /** 查找复合控件并设置id */
128 private void setCheckedId(ViewGroup vg) {
129 int len = vg.getChildCount();
130 for (int i = 0; i < len; i++) {
131 if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态
132 final RadioButton button = (RadioButton) vg.getChildAt(i);
133 int id = button.getId();
134 // generates an id if it's missing
135 if (id == View.NO_ID) {
136 id = button.hashCode();
137 button.setId(id);
138 }
139 button.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
140 } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置
141 ViewGroup childVg = (ViewGroup) vg.getChildAt(i);
142 setCheckedId(childVg);
143 }
144 }
145 }
146
147 /** 查找radioButton控件 */
148 public RadioButton findRadioButton(ViewGroup group) {
149 RadioButton resBtn = null;
150 int len = group.getChildCount();
151 for (int i = 0; i < len; i++) {
152 if (group.getChildAt(i) instanceof RadioButton) {
153 resBtn = (RadioButton) group.getChildAt(i);
154 } else if (group.getChildAt(i) instanceof ViewGroup) {
155 resBtn = findRadioButton((ViewGroup) group.getChildAt(i));
156 findRadioButton((ViewGroup) group.getChildAt(i));
157 break;
158 }
159 }
160 return resBtn;
161 }
162
163 /** 返回当前radiobutton控件的count */
164 public int getRadioButtonCount() {
165 return radioButtons.size();
166 }
167
168 /** 返回当前index的radio */
169 public RadioButton getRadioButton(int index) {
170 return radioButtons.get(index);
171 }
172
173 /**
174 * <p>
175 * Sets the selection to the radio button whose identifier is passed in
176 * parameter. Using -1 as the selection identifier clears the selection;
177 * such an operation is equivalent to invoking {@link #clearCheck()}.
178 * </p>
179 *
180 * @param id
181 * the unique id of the radio button to select in this group
182 *
183 * @see #getCheckedRadioButtonId()
184 * @see #clearCheck()
185 */
186 public void check(int id) {
187 // don't even bother
188 if (id != -1 && (id == mCheckedId)) {
189 return;
190 }
191
192 if (mCheckedId != -1) {
193 setCheckedStateForView(mCheckedId, false);
194 }
195
196 if (id != -1) {
197 setCheckedStateForView(id, true);
198 }
199
200 setCheckedId(id);
201 }
202
203 private void setCheckedId(int id) {
204 mCheckedId = id;
205 if (mOnCheckedChangeListener != null) {
206 mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
207 }
208 }
209
210 private void setCheckedStateForView(int viewId, boolean checked) {
211 View checkedView = findViewById(viewId);
212 if (checkedView != null && checkedView instanceof RadioButton) {
213 ((RadioButton) checkedView).setChecked(checked);
214 }
215 }
216
217 /**
218 * <p>
219 * Returns the identifier of the selected radio button in this group. Upon
220 * empty selection, the returned value is -1.
221 * </p>
222 *
223 * @return the unique id of the selected radio button in this group
224 *
225 * @see #check(int)
226 * @see #clearCheck()
227 */
228 public int getCheckedRadioButtonId() {
229 return mCheckedId;
230 }
231
232 /**
233 * <p>
234 * Clears the selection. When the selection is cleared, no radio button in
235 * this group is selected and {@link #getCheckedRadioButtonId()} returns
236 * null.
237 * </p>
238 *
239 * @see #check(int)
240 * @see #getCheckedRadioButtonId()
241 */
242 public void clearCheck() {
243 check(-1);
244 }
245
246 /**
247 * <p>
248 * Register a callback to be invoked when the checked radio button changes
249 * in this group.
250 * </p>
251 *
252 * @param listener
253 * the callback to call on checked state change
254 */
255 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
256 mOnCheckedChangeListener = listener;
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 @Override
263 public LayoutParams generateLayoutParams(AttributeSet attrs) {
264 return new FlowRadioGroup.LayoutParams(getContext(), attrs);
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 @Override
271 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
272 return p instanceof FlowRadioGroup.LayoutParams;
273 }
274
275 @Override
276 protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
277 return new LayoutParams(LayoutParams.WRAP_CONTENT,
278 LayoutParams.WRAP_CONTENT);
279 }
280
281 /**
282 * <p>
283 * This set of layout parameters defaults the width and the height of the
284 * children to {@link #WRAP_CONTENT} when they are not specified in the XML
285 * file. Otherwise, this class ussed the value read from the XML file.
286 * </p>
287 *
288 * <p>
289 * See {@link android.R.styleable#LinearLayout_Layout LinearLayout
290 * Attributes} for a list of all child view attributes that this class
291 * supports.
292 * </p>
293 *
294 */
295 public static class LayoutParams extends LinearLayout.LayoutParams {
296 /**
297 * {@inheritDoc}
298 */
299 public LayoutParams(Context c, AttributeSet attrs) {
300 super(c, attrs);
301 }
302
303 /**
304 * {@inheritDoc}
305 */
306 public LayoutParams(int w, int h) {
307 super(w, h);
308 }
309
310 /**
311 * {@inheritDoc}
312 */
313 public LayoutParams(int w, int h, float initWeight) {
314 super(w, h, initWeight);
315 }
316
317 /**
318 * {@inheritDoc}
319 */
320 public LayoutParams(ViewGroup.LayoutParams p) {
321 super(p);
322 }
323
324 /**
325 * {@inheritDoc}
326 */
327 public LayoutParams(MarginLayoutParams source) {
328 super(source);
329 }
330
331 /**
332 * <p>
333 * Fixes the child's width to
334 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the
335 * child's height to
336 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when not
337 * specified in the XML file.
338 * </p>
339 *
340 * @param a
341 * the styled attributes set
342 * @param widthAttr
343 * the width attribute to fetch
344 * @param heightAttr
345 * the height attribute to fetch
346 */
347 @Override
348 protected void setBaseAttributes(TypedArray a, int widthAttr,
349 int heightAttr) {
350
351 if (a.hasValue(widthAttr)) {
352 width = a.getLayoutDimension(widthAttr, "layout_width");
353 } else {
354 width = WRAP_CONTENT;
355 }
356
357 if (a.hasValue(heightAttr)) {
358 height = a.getLayoutDimension(heightAttr, "layout_height");
359 } else {
360 height = WRAP_CONTENT;
361 }
362 }
363 }
364
365 /**
366 * <p>
367 * Interface definition for a callback to be invoked when the checked radio
368 * button changed in this group.
369 * </p>
370 */
371 public interface OnCheckedChangeListener {
372 /**
373 * <p>
374 * Called when the checked radio button has changed. When the selection
375 * is cleared, checkedId is -1.
376 * </p>
377 *
378 * @param group
379 * the group in which the checked radio button has changed
380 * @param checkedId
381 * the unique identifier of the newly checked radio button
382 */
383 public void onCheckedChanged(FlowRadioGroup group, int checkedId);
384 }
385
386 private class CheckedStateTracker implements
387 CompoundButton.OnCheckedChangeListener {
388 public void onCheckedChanged(CompoundButton buttonView,
389 boolean isChecked) {
390 // prevents from infinite recursion
391 if (mProtectFromCheckedChange) {
392 return;
393 }
394
395 mProtectFromCheckedChange = true;
396 if (mCheckedId != -1) {
397 setCheckedStateForView(mCheckedId, false);
398 }
399 mProtectFromCheckedChange = false;
400
401 int id = buttonView.getId();
402 setCheckedId(id);
403 }
404 }
405
406 /**
407 * <p>
408 * A pass-through listener acts upon the events and dispatches them to
409 * another listener. This allows the table layout to set its own internal
410 * hierarchy change listener without preventing the user to setup his.
411 * </p>
412 */
413 private class PassThroughHierarchyChangeListener implements
414 ViewGroup.OnHierarchyChangeListener {
415 private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
416
417 public void onChildViewAdded(View parent, View child) {
418 if (parent == FlowRadioGroup.this && child instanceof RadioButton) {
419 int id = child.getId();
420 // generates an id if it's missing
421 if (id == View.NO_ID) {
422 id = child.hashCode();
423 child.setId(id);
424 }
425 ((RadioButton) child)
426 .setOnCheckedChangeListener(mChildOnCheckedChangeListener);
427 } else if (parent == FlowRadioGroup.this
428 && child instanceof ViewGroup) {// 如果是复合控件
429 // 查找并设置id
430 setCheckedId((ViewGroup) child);
431 }
432
433 if (mOnHierarchyChangeListener != null) {
434 mOnHierarchyChangeListener.onChildViewAdded(parent, child);
435 }
436 }
437
438 public void onChildViewRemoved(View parent, View child) {
439 if (parent == FlowRadioGroup.this && child instanceof RadioButton) {
440 ((RadioButton) child).setOnCheckedChangeListener(null);
441 } else if (parent == FlowRadioGroup.this
442 && child instanceof ViewGroup) {
443 findRadioButton((ViewGroup) child).setOnCheckedChangeListener(
444 null);
445 }
446 if (mOnHierarchyChangeListener != null) {
447 mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
448 }
449 }
450 }
451 }
简单讲解下我的实现:
1)在addview方法中,加上判断,当前子控件是否为viewgroup类型
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof RadioButton) {
final RadioButton button = (RadioButton) child;
radioButtons.add(button);//将找到的控件添加到集合中
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
} else if (child instanceof ViewGroup) {// 如果是复合控件
// 遍历复合控件
ViewGroup vg = ((ViewGroup) child);
setCheckedView(vg);
}
super.addView(child, index, params);
}
/** 查找复合控件并设置radiobutton */
private void setCheckedView(ViewGroup vg) {
int len = vg.getChildCount();
for (int i = 0; i < len; i++) {
if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态
final RadioButton button = (RadioButton) vg.getChildAt(i);
// 添加到容器
radioButtons.add(button);
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
} else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置
ViewGroup childVg = (ViewGroup) vg.getChildAt(i);
setCheckedView(childVg);
}
}
}
2)定义一个数组存放当前所有查到到的radiobutton;
3)在onChildViewAdded方法中,判断新添加的子控件是否为viewgroup类型
else if (parent == FlowRadioGroup.this
&& child instanceof ViewGroup) {// 如果是复合控件
// 查找并设置id
setCheckedId((ViewGroup) child);
}
/** 查找复合控件并设置id */
private void setCheckedId(ViewGroup vg) {
int len = vg.getChildCount();
for (int i = 0; i < len; i++) {
if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态
final RadioButton button = (RadioButton) vg.getChildAt(i);
int id = button.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
id = button.hashCode();
button.setId(id);
}
button.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
} else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置
ViewGroup childVg = (ViewGroup) vg.getChildAt(i);
setCheckedId(childVg);
}
}
}
下面是DivisionEditText的源码;
1 package com.newgame.sdk.view;
2
3 import android.content.Context;
4 import android.text.Editable;
5 import android.text.TextWatcher;
6 import android.util.AttributeSet;
7 import android.view.View;
8 import android.widget.EditText;
9
10 /**
11 * 分割输入框
12 *
13 * @author Administrator
14 *
15 */
16 public class DivisionEditText extends EditText {
17
18 /* 每组的长度 */
19 private Integer eachLength = 4;
20 /* 分隔符 */
21 private String delimiter = " ";
22
23 private String text = "";
24
25 public DivisionEditText(Context context) {
26 super(context);
27 init();
28 }
29
30 public DivisionEditText(Context context, AttributeSet attrs) {
31 super(context, attrs);
32 init();
33
34 }
35
36 public DivisionEditText(Context context, AttributeSet attrs, int defStyle) {
37 super(context, attrs, defStyle);
38 init();
39 }
40
41 /**
42 * 初始化
43 */
44 public void init() {
45
46 // 内容变化监听
47 this.addTextChangedListener(new DivisionTextWatcher());
48 // 获取焦点监听
49 this.setOnFocusChangeListener(new DivisionFocusChangeListener());
50 }
51
52 /**
53 * 文本监听
54 *
55 * @author Administrator
56 *
57 */
58 private class DivisionTextWatcher implements TextWatcher {
59
60 @Override
61 public void afterTextChanged(Editable s) {
62 }
63
64 @Override
65 public void beforeTextChanged(CharSequence s, int start, int count,
66 int after) {
67 }
68
69 @Override
70 public void onTextChanged(CharSequence s, int start, int before,
71 int count) {
72 // 统计个数
73 int len = s.length();
74 if (len < eachLength)// 长度小于要求的数
75 return;
76 if (count > 1) {
77 return;
78 }
79 // 如果包含空格,就清除
80 char[] chars = s.toString().replace(" ", "").toCharArray();
81 len = chars.length;
82 // 每4个分组,加上空格组合成新的字符串
83 StringBuffer sb = new StringBuffer();
84 for (int i = 0; i < len; i++) {
85 if (i % eachLength == 0 && i != 0)// 每次遍历到4的倍数,就添加一个空格
86 {
87 sb.append(" ");
88 sb.append(chars[i]);// 添加字符
89 } else {
90 sb.append(chars[i]);// 添加字符
91 }
92 }
93 // 设置新的字符到文本
94 // System.out.println("*************" + sb.toString());
95 text = sb.toString();
96 setText(text);
97 setSelection(text.length());
98 }
99 }
100
101 /**
102 * 获取焦点监听
103 *
104 * @author Administrator
105 *
106 */
107 private class DivisionFocusChangeListener implements OnFocusChangeListener {
108
109 @Override
110 public void onFocusChange(View v, boolean hasFocus) {
111 if (hasFocus) {
112 // 设置焦点
113 setSelection(getText().toString().length());
114 }
115 }
116 }
117
118 /** 得到每组个数 */
119 public Integer getEachLength() {
120 return eachLength;
121 }
122
123 /** 设置每组个数 */
124 public void setEachLength(Integer eachLength) {
125 this.eachLength = eachLength;
126 }
127
128 /** 得到间隔符 */
129 public String getDelimiter() {
130 return delimiter;
131 }
132
133 /** 设置间隔符 */
134 public void setDelimiter(String delimiter) {
135 this.delimiter = delimiter;
136 }
137
138 }
上面代码实现逻辑:在TextWatcher的onTextChanged方法中判断当前输入的字符,然后没4位添加一个空格,组成新的字符
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// 统计个数
int len = s.length();
if (len < eachLength)// 长度小于要求的数
return;
if (count > 1) {// 设置新字符串的时候,直接返回
return;
}
// 如果包含空格,就清除
char[] chars = s.toString().replace(" ", "").toCharArray();
len = chars.length;
// 每4个分组,加上空格组合成新的字符串
StringBuffer sb = new StringBuffer();
for (int i = 0; i < len; i++) {
if (i % eachLength == 0 && i != 0)// 每次遍历到4的倍数,就添加一个空格
{
sb.append(" ");
sb.append(chars[i]);// 添加字符
} else {
sb.append(chars[i]);// 添加字符
}
}
// 设置新的字符到文本
// System.out.println("*************" + sb.toString());
text = sb.toString();
setText(text);
setSelection(text.length());
}
还有其他两个自定义控件也在项目中,这里界面没体现出来,我已经放在项目中了;
欢迎大家找出代码中的存在bug!!!!
最后附上代码下载地址:http://www.eoeandroid.com/forum.php?mod=attachment&aid=MTIwMDM1fDM5NTYzZjQ3fDEzOTY0Mjc4NDF8NzU4MzI1fDMyODQyNw%3D%3D