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
- 可無限巢狀選擇的RadioGroup,以及可任意定義佈局的RadioButton巢狀
- Android自定義View:View(二)AndroidView
- Android XML佈局報錯:android/view/View$OnUnhandledKeyEventListenerAndroidXMLView
- 【譯】Android Architecture - ViewModel 與 View 的通訊AndroidView
- Android ViewGroup&&RadioGroup 換行流式標籤佈局AndroidView
- Android View 系統 1 - View樹AndroidView
- Android自定義view-自繪ViewAndroidView
- Android View post 方法AndroidView
- Android進階系列:八、自定義View之音訊抖動動效AndroidView音訊
- Android自定義View整合AndroidView
- Android View體系(4)AndroidView
- Android View 的工作原理AndroidView
- Android新手引導ViewAndroidView
- [Android]關於Android子view超出父view無法響應點選事件AndroidView事件
- Android View 的事件體系AndroidView事件
- 重拾Android自定義ViewAndroidView
- Android自定義View:ViewGroup(三)AndroidView
- Android View的工作原理(上)AndroidView
- Android 自定義 View 之 LeavesLoadingAndroidView
- android view 擴充套件方法AndroidView套件
- Android使用者介面之常用控制元件RadioGroup、CheckBox 2018/8/10Android控制元件
- Android View 原始碼解析(三) – View的繪製過程AndroidView原始碼
- Attempt to invoke virtual method ‘int android.view.View.getImportantForAccessibility()‘ on a null obAndroidViewImportNull
- View繪製01-Android渲染系統中的ViewViewAndroid
- 【朝花夕拾】Android自定義View篇之(十一)View的滑動,彈性滑動與自定義PagerViewAndroidView
- 你需要了解下Android View的更新requestLayout與重繪invalidateAndroidView
- Android自定義View之分貝儀AndroidView
- Android View的繪製過程AndroidView
- Android自定義View之捲尺AndroidView
- Android自定義View注意事項AndroidView
- Android自定義View-卷軸AndroidView
- Android自定義View 水波氣泡AndroidView
- Android 自定義View 點贊效果AndroidView
- Android 自定義View基礎(一)AndroidView
- android自定義View——座標系AndroidView
- Android View 原始碼解析(一) - setContentViewAndroidView原始碼
- Android日常學習:Android檢視動畫-View AnimationAndroid動畫View