Android View系列---RadioGroup與RadioButton
RadioGroup與RadioButton配合實現一組資料的單選問題。
插播一條資訊,在設定RadioButton的textColor的選中效果時,不能在drawable中建立想xml,得在res/color檔案中建立xml,然後引用。
radioButton.setTextColor(getResources().getColorStateList(R.color.xxx));
這個過程中,需要注意幾點。
- RadioButton 設定前面小圓點消失 radioButton.setButtonDrawable(null)
- RadioGroup 下面的RadioButton不能用其他控制元件包裹,否則就會是一個一個單獨的RadioButton。
- 在動態建立RadioButton的時候,需要設定margin時,需要使用RadioGroup.LayoutParams 來建立佈局引數,不然設定margin不起作用。
- 如何實現在RadioGroup與RadioButton配合時,現在點選兩次RadioButton,取消選中
對以上問題,具體我們分析下:
-
為什麼RadioGroup的直接子控制元件必須是RadioButt呢?
先來看下RadioGroup的init()方法private void init() { // tracks children radio buttons checked state 追蹤RadioButton的選中狀態 mChildOnCheckedChangeListener = new CheckedStateTracker(); // 監聽層級變化 ViewGroup的子View移除和新增都會觸發相對應的方法。 mPassThroughListener = new PassThroughHierarchyChangeListener(); super.setOnHierarchyChangeListener(mPassThroughListener); } /** * {@inheritDoc} 給使用者提供一個方法,可以自己實現層級的監聽 */ @Override public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { // the user listener is delegated to our pass-through listener mPassThroughListener.mOnHierarchyChangeListener = listener; }
再看下,PassThroughHierarchyChangeListener 實現的新增和移除的方法
private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ public void onChildViewAdded(View parent, View child) { // 關鍵點一: 如果child是RadioButton,才會執行 if (parent == RadioGroup.this && child instanceof RadioButton) { int id = child.getId(); // generates an id if it's missing if (id == View.NO_ID) { id = View.generateViewId(); child.setId(id); } // 關鍵點二:把追蹤RadioButton選擇狀態的監聽,設定到radiobutoon上,以實現監聽 ((RadioButton) child).setOnCheckedChangeWidgetListener( mChildOnCheckedChangeListener); } // 如果使用者自己定義,會繼續走使用者自己定義的方法 if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewAdded(parent, child); } } 。。。。 }
接著,我們來看下RadioGroup的addView方法
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof RadioButton) {
final RadioButton button = (RadioButton) child;
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
}
// 父類ViewGroup的addView方法
super.addView(child, index, params);
}
ViewGroup.java
從原始碼裡可以 addView()方法裡面呼叫了 addViewInner()方法,在裡面呼叫了disparchViewAdd(child)方法。
addView() —> addViewInner() —>dispatchViewAdd()
void dispatchViewAdded(View child) {
onViewAdded(child);
// 在RadioGroup init方法中,設定了該listener。在這裡回撥到onChildViewAdded()方法。再為radiobutton設定了OnCheckedChangeWidgetListener
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
}
理清楚他們之間的關聯,現在該是點選的時候了。
之前介紹過onclick方法,之所以能執行,是整個事件分發完之後,執行了performClick()方法,然後執行clicklistener的onclick回撥方法。
RadioButton繼承了CompoundButton。 裡面的performClick方法
CompoundButton.java
@Override
public boolean performClick() {
// 先執行radioButton的toggle方法
toggle();
// 執行 onclick方法。
final boolean handled = super.performClick();
if (!handled) {
// View only makes a sound effect if the onClickListener was
// called, so we'll need to make one here instead.
playSoundEffect(SoundEffectConstants.CLICK);
}
return handled;
}
public void toggle() {
setChecked(!mChecked);
}
RadioButton.java
/**
* {@inheritDoc}
* <p>
* If the radio button is already checked, this method will not toggle the radio button.
*/
@Override
public void toggle() {
// we override to prevent toggle when the radio is already
// checked (as opposed to check boxes widgets)
// 關鍵點四:如果該RadioButton已經選中了,就不執行了,這也是RadioButton一旦被選擇了,就不能取消選中的根本原因。
if (!isChecked()) {
super.toggle();
}
}
那RadioButton怎麼和RadioGroup聯絡到一起的呢?
RadioButton的父類CompoundButton的toggle
public void toggle() {
setChecked(!mChecked);
}
/**
* <p>Changes the checked state of this button.</p>
* 真正改變RadioButton狀態的方法。
* @param checked true to check the button, false to uncheck it
*/
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
// 重新整理選中態的UI
refreshDrawableState();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
// Avoid infinite recursions if setChecked() is called from a listener
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
// 由前面關鍵點二可知,在RadioGroup中設定這個listener,所以回撥到RadioGroup中
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
再來看下CheckedChangeWidgetListener的具體實現 CheckedStateTracker
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return;
}
mProtectFromCheckedChange = true;
//關鍵點三:將其他的RadioButton置為未選中 mCheckedId是之前選擇的View Id,
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
int id = buttonView.getId();
// 更新 mCheckedId
setCheckedId(id);
}
}
private void setCheckedStateForView(int viewId, boolean checked) {
// 找到之前選中的RadioButton。設定它的選中狀態。
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
以上分析了整個RadioGroup為什麼子View必須是RadioButton 才能起到一組單選的效果,以及整個流程。
2、怎麼實現RadioButton點選兩次,取消選中呢?
由關鍵點四,可以知道如果RadioButton選中了,就不會執行setChecked()方法也就無法通知RadioGroup。
但是他扔會走touch方法、click方法。
我寫的思路就是,在RadioButton的點選事件裡面,人為的設定radiobutton的選中狀態。
但是RadioButton的click事件是發生在RadioButton自己的setChecked()之後,所以
radioButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
// 不管該控制元件的狀態之前是選中態還是未選中態,這個時候,都已經是選中狀態了
// 所以這個時候,radioButton的isChecked()得到的都是true
// 所以關鍵是找到之前的選中的是哪個控制元件。
if (checkedId == v.getId()){
// 說明是第二次點選該控制元件,所以要做清除選中邏輯
// 清除選中,其實縱觀整個RadioGroup,只有當前的RadioButton被選中了,而此時恰恰需要清除選中,則只需要把RadioGroup全部清空狀態即可。
radioGroup.clearCheck();
}
}
})
根據View的整個事件分發機制,click事件,是最後觸發的。
所以我們可以在touch事件中獲得到之前選中的狀態。
又根據RadioGroup提供了一個方法getCheckedRadioButtonId(),返回選中態RadioButton的id
int checkedId = -1;
radioButton.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event){
// 這時候,新的button狀態還沒有設定,所以返回的是之前選中的button
checkedId = radioGroup.getCheckedRadioButtonId();
// 不消耗掉該事件,接著往下傳遞給click事件。
return false;
}
})
這樣就是整個過程了。
相關文章
- Android開發之RadioGroup與RadioButton控制元件使用Android控制元件
- Android 如何獲取RadioGroup選中RadioButton的內容Android
- 直播系統搭建,Android使用RadioGroup+RadioButton實現導航欄Android
- Android UI系列-----CheckBox和RadioButton(1)AndroidUI
- Android UI控制元件系列:RadioButton(單選按鈕)AndroidUI控制元件
- android RadioButton的問題Android
- 可無限巢狀選擇的RadioGroup,以及可任意定義佈局的RadioButton巢狀
- Android 自定義View實戰系列 :時間軸AndroidView
- Android ViewAndroidView
- Android自定義View:MeasureSpec的真正意義與View大小控制AndroidView
- Android自定義View:View(二)AndroidView
- Android RadioGroup多行顯示,解決單選問Android
- Android View 系統 1 - View樹AndroidView
- android view 分析AndroidView
- Android XML佈局報錯:android/view/View$OnUnhandledKeyEventListenerAndroidXMLView
- Android程式碼隱藏RadioButton預設圖示Android
- Android自定義view-自繪ViewAndroidView
- Android進階系列:八、自定義View之音訊抖動動效AndroidView音訊
- 【譯】Android Architecture - ViewModel 與 View 的通訊AndroidView
- Android View原始碼解讀:淺談DecorView與ViewRootImplAndroidView原始碼
- Android View post 方法AndroidView
- Android 自定義viewAndroidView
- Android: 自定義ViewAndroidView
- Android view架構AndroidView架構
- Android View總結AndroidView
- Android ViewGroup&&RadioGroup 換行流式標籤佈局AndroidView
- Jerry的CDS view自學系列View
- Android radioButton只設定圖片是居中顯示Android
- Android中如何設定RadioButton在文字的右邊Android
- c# radiobuttonC#
- jQuery 操作radiobuttonjQuery
- 快速理解android View的測量onMeasure()與MeasureSpecAndroidView
- Android View 的工作原理AndroidView
- Android自定義View整合AndroidView
- Android View體系(4)AndroidView
- Android新手引導ViewAndroidView
- Android View 使用總結AndroidView
- Android View繪製流程AndroidView