前言
非常喜歡用radiobutton+radiogroup做tabs,能自動處理選中等效果,但是自帶的radiogroup不支援嵌套radiobutton(從源碼可看出僅僅是判斷子控件是不是radiobutton),本文參考radiogroup修改了一個支援嵌套compoundbutton的控件,非常實用。
聲明
歡迎轉載,但請保留文章原始出處:)
部落格園:http://www.cnblogs.com
農民伯伯: http://over140.cnblogs.com
正文
/**
* 支援嵌套compoundbutton的nestradiogroup
*
* @author 農民伯伯 http://www.cnblogs.com/over140/
*
*/
public class nestradiogroup extends linearlayout {
// holds the checked id; the selection is empty by default
private int mcheckedid = -1;
// tracks children radio buttons checked state
private compoundbutton.oncheckedchangelistener mchildoncheckedchangelistener;
// when true, moncheckedchangelistener discards events
private boolean mprotectfromcheckedchange = false;
private oncheckedchangelistener moncheckedchangelistener;
private passthroughhierarchychangelistener mpassthroughlistener;
/**
* {@inheritdoc}
*/
public nestradiogroup(context context) {
super(context);
init();
}
public nestradiogroup(context context, attributeset attrs) {
super(context, attrs);
private void init() {
mcheckedid = view.no_id;
setorientation(horizontal);
mchildoncheckedchangelistener = new checkedstatetracker();
mpassthroughlistener = new passthroughhierarchychangelistener();
super.setonhierarchychangelistener(mpassthroughlistener);
@override
public void setonhierarchychangelistener(onhierarchychangelistener listener) {
// the user listener is delegated to our pass-through listener
mpassthroughlistener.monhierarchychangelistener = listener;
protected void onfinishinflate() {
super.onfinishinflate();
// checks the appropriate radio button as requested in the xml file
if (mcheckedid != view.no_id) {
mprotectfromcheckedchange = true;
setcheckedstateforview(mcheckedid, true);
mprotectfromcheckedchange = false;
setcheckedid(mcheckedid);
}
/** 遞歸查找具有選中屬性的子控件 */
private static compoundbutton findcheckedview(view child) {
if (child instanceof compoundbutton)
return (compoundbutton) child;
if (child instanceof viewgroup) {
viewgroup group = (viewgroup) child;
for (int i = 0, j = group.getchildcount(); i < j; i++) {
compoundbutton check = findcheckedview(group.getchildat(i));
if (check != null)
return check;
}
return null;//沒有找到
public void addview(view child, int index, viewgroup.layoutparams params) {
final compoundbutton view = findcheckedview(child);
if (view != null) {
if (view.ischecked()) {
mprotectfromcheckedchange = true;
if (mcheckedid != -1) {
setcheckedstateforview(mcheckedid, false);
}
mprotectfromcheckedchange = false;
setcheckedid(view.getid());
super.addview(child, index, params);
* <p>sets the selection to the radio button whose identifier is passed in
* parameter. using -1 as the selection identifier clears the selection;
* such an operation is equivalent to invoking {@link #clearcheck()}.</p>
*
* @param id the unique id of the radio button to select in this group
* @see #getcheckedradiobuttonid()
* @see #clearcheck()
public void check(int id) {
// don't even bother
if (id != -1 && (id == mcheckedid)) {
return;
if (mcheckedid != -1) {
setcheckedstateforview(mcheckedid, false);
if (id != -1) {
setcheckedstateforview(id, true);
setcheckedid(id);
private void setcheckedid(int id) {
mcheckedid = id;
if (moncheckedchangelistener != null) {
moncheckedchangelistener.oncheckedchanged(this, mcheckedid);
private void setcheckedstateforview(int viewid, boolean checked) {
view checkedview = findviewbyid(viewid);
if (checkedview != null && checkedview instanceof compoundbutton) {
((compoundbutton) checkedview).setchecked(checked);
* <p>returns the identifier of the selected radio button in this group.
* upon empty selection, the returned value is -1.</p>
* @return the unique id of the selected radio button in this group
* @see #check(int)
* @attr ref android.r.styleable#nestradiogroup_checkedbutton
public int getcheckedradiobuttonid() {
return mcheckedid;
* <p>clears the selection. when the selection is cleared, no radio button
* in this group is selected and {@link #getcheckedradiobuttonid()} returns
* null.</p>
public void clearcheck() {
check(-1);
* <p>register a callback to be invoked when the checked radio button
* changes in this group.</p>
* @param listener the callback to call on checked state change
public void setoncheckedchangelistener(oncheckedchangelistener listener) {
moncheckedchangelistener = listener;
public layoutparams generatelayoutparams(attributeset attrs) {
return new nestradiogroup.layoutparams(getcontext(), attrs);
protected boolean checklayoutparams(viewgroup.layoutparams p) {
return p instanceof nestradiogroup.layoutparams;
protected linearlayout.layoutparams generatedefaultlayoutparams() {
return new layoutparams(layoutparams.wrap_content, layoutparams.wrap_content);
* <p>this set of layout parameters defaults the width and the height of
* the children to {@link #wrap_content} when they are not specified in the
* xml file. otherwise, this class ussed the value read from the xml file.</p>
* <p>see
* {@link android.r.styleable#linearlayout_layout linearlayout attributes}
* for a list of all child view attributes that this class supports.</p>
public static class layoutparams extends linearlayout.layoutparams {
/**
* {@inheritdoc}
*/
public layoutparams(context c, attributeset attrs) {
super(c, attrs);
public layoutparams(int w, int h) {
super(w, h);
public layoutparams(int w, int h, float initweight) {
super(w, h, initweight);
public layoutparams(viewgroup.layoutparams p) {
super(p);
public layoutparams(marginlayoutparams source) {
super(source);
* <p>fixes the child's width to
* {@link android.view.viewgroup.layoutparams#wrap_content} and the child's
* height to {@link android.view.viewgroup.layoutparams#wrap_content}
* when not specified in the xml file.</p>
*
* @param a the styled attributes set
* @param widthattr the width attribute to fetch
* @param heightattr the height attribute to fetch
@override
protected void setbaseattributes(typedarray a, int widthattr, int heightattr) {
if (a.hasvalue(widthattr)) {
width = a.getlayoutdimension(widthattr, "layout_width");
} else {
width = wrap_content;
if (a.hasvalue(heightattr)) {
height = a.getlayoutdimension(heightattr, "layout_height");
height = wrap_content;
* <p>interface definition for a callback to be invoked when the checked
* radio button changed in this group.</p>
public interface oncheckedchangelistener {
* <p>called when the checked radio button has changed. when the
* selection is cleared, checkedid is -1.</p>
* @param group the group in which the checked radio button has changed
* @param checkedid the unique identifier of the newly checked radio button
public void oncheckedchanged(nestradiogroup group, int checkedid);
private class checkedstatetracker implements compoundbutton.oncheckedchangelistener {
public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) {
// prevents from infinite recursion
if (mprotectfromcheckedchange) {
return;
if (mcheckedid != -1) {
setcheckedstateforview(mcheckedid, false);
int id = buttonview.getid();
setcheckedid(id);
* <p>a pass-through listener acts upon the events and dispatches them
* to another listener. this allows the table layout to set its own internal
* hierarchy change listener without preventing the user to setup his.</p>
private class passthroughhierarchychangelistener implements viewgroup.onhierarchychangelistener {
private viewgroup.onhierarchychangelistener monhierarchychangelistener;
@targetapi(build.version_codes.jelly_bean_mr1)
public void onchildviewadded(view parent, view child) {
if (parent == nestradiogroup.this) {
compoundbutton view = findcheckedview(child);//查找子控件
if (view != null) {
int id = view.getid();
// generates an id if it's missing
if (id == view.no_id && build.version.sdk_int >= build.version_codes.jelly_bean_mr1) {
id = view.generateviewid();
view.setid(id);
}
view.setoncheckedchangelistener(mchildoncheckedchangelistener);
if (monhierarchychangelistener != null) {
monhierarchychangelistener.onchildviewadded(parent, child);
public void onchildviewremoved(view parent, view child) {
view.setoncheckedchangelistener(null);
monhierarchychangelistener.onchildviewremoved(parent, child);
}
代碼說明
代碼主要是仿照radiogroup改寫,主要是findcheckedview方法遞歸查找具有選中屬性的子控件。
用法
<com.xxx.ui.view.nestradiogroup
android:id="@+id/main_radio"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<radiobutton
android:id="@+id/radio_button0"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:checked="true"
android:text="@string/bottom_feed" />
android:id="@+id/radio_button1"
android:text="@string/bottom_square" />
<relativelayout
android:layout_height="fill_parent"
android:orientation="horizontal" >
<radiobutton
android:id="@+id/radio_button2"
style="@style/title_tab_text_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerinparent="true"
android:text="@string/bottom_message" />
<imageview
android:id="@+id/new_message_tips"
android:layout_margintop="10dip"
android:layout_torightof="@+id/radio_button2"
android:src="@drawable/news_tips_red" />
</relativelayout>
</com.xxx.ui.view.nestradiogroup>
代碼說明
1、實作非常常見的tabs效果,結合viewpager來使用,new_message_tips可以是一個類似微信右上角的小紅圈,用來提醒有新的消息。
2、view.generateviewid需要4.2以上才能使用,是以最好自己設定id
轉自:http://www.cnblogs.com/over140/p/3795877.html