CompoundButton 源码分析
原文地址:https://github.com/Tikitoo/AndroidSdkSourceAnalysis/blob/master/article/CompoundButton%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md
?
CompoundButton 是一個有兩種狀態(選中和未選中 / checkd unchecked)的Button。當你按下(pressed)或者點擊(clicked),它的狀態會自動改變。
特點
- 是一個抽象類(abstract),所以我們不能直接使用它,只有自定義實現或者系統已經提供的它的一直子類(ToggleButton,Checkbox,RadioButton 等等)。
- 繼承自Button,而Button 繼承自TextView,所以Button,TextView 的特性CompoundButton 都是具備的。
- 實現自Checkable 接口(interface),利用它可以設置狀態(setChecked(boolean checked)),獲取狀態(isChecked())和切換狀態(toggle())。
最后的效果圖就是這樣的。
分析
// 選中和未選中的狀態 private boolean mChecked;private boolean mBroadcasting;private Drawable mButtonDrawable;private ColorStateList mButtonTintList = null; // 就是水波紋和背景顏色混合的方式 private PorterDuff.Mode mButtonTintMode = null;private boolean mHasButtonTint = false; private boolean mHasButtonTintMode = false;// 狀態監聽 private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener;?
這是一些局部變量,在后面的分析會用到。
我們先來看看CompoundButton 自定義控件有哪些屬性?\data\res\values\attrs.xml
<declare-styleable name="CompoundButton"><!-- 設置狀態 true: 選中; false: 未選中 --><attr name="checked" format="boolean" /><!-- 繪制按鈕圖形,一般為Drawable 資源 (e.g. checkbox, radio button, etc). --><attr name="button" format="reference" /><!-- 對繪制的按鈕圖形著色 --><attr name="buttonTint" format="color" /><!-- 對著色設置模式 --><attr name="buttonTintMode"><enum name="src_over" value="3" />...</attr></declare-styleable>?
然后再來看看怎么繪制,先來看看構造方法
public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);// 這里的獲取自定義的CompoundButton 就是上面的定義的CompoundButtonfinal TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);// 用于繪制按鈕圖形final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);if (d != null) {setButtonDrawable(d);}// 對繪制的按鈕圖形著色設置模式if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {mButtonTintMode = Drawable.parseTintMode(a.getInt(R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);mHasButtonTintMode = true;}// 對繪制的按鈕圖形著色if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint);mHasButtonTint = true;}// 設置狀態final boolean checked = a.getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);setChecked(checked);a.re cycle();applyButtonTint(); }?
在構造方法中,獲取自定義屬性的各個屬性,
- button:用于繪制按鈕圖形,然后調用setButtonDrawable() 來繪制。
- buttonTint:繪制的按鈕著色。使用一個boolean 標識符來設置的,然后會在applyButtonTint() 中統一處理。它們兩個分別用作給和
- buttonTintMode:和設置著色模式。設置方式和buttonTint 幾乎一樣。不過它的一些屬性,參考這篇文章來看看具體不同的著色模式效果是怎么樣的。android5.x新特性之Tinting
- checked:是設置選中狀態,在setChecked() 中設置。
繪制按鈕圖形
@Nullable public void setButtonDrawable(@Nullable Drawable drawable) {if (mButtonDrawable != drawable) {if (mButtonDrawable != null) {// 取消對View 的引用mButtonDrawable.setCallback(null);// 取消繪制對象相關聯的調度,當我們重新繪制一個Drawable,可以調用此方法 unscheduleDrawable(mButtonDrawable);}mButtonDrawable = drawable;if (drawable != null) {drawable.setCallback(this);drawable.setLayoutDirection(getLayoutDirection());if (drawable.isStateful()) {drawable.setState(getDrawableState());}drawable.setVisible(getVisibility() == VISIBLE, false);setMinHeight(drawable.getIntrinsicHeight());applyButtonTint();}} }?
這個方法,就是用于繪制按鈕圖形,首先會判斷我們設置的Drawable 和初始會的mButtonDrawable 是否相等,如果不等,將會對初始化的mButtonDrawable 對象,取消對View 的引用(setCallback(null)),并且取消mButtonDrawable 相關聯調度(unscheduleDrawable()),然后將我們新設置的Drawable 賦值給mButtonDrawable 對象,然后再設置引用,設置布局的方向,然后判斷如果狀態改變時,重新設置狀態。最后調用applyButtonTint()。
著色
private void applyButtonTint() {if (mButtonDrawable != null && (mHasButtonTint || mHasButtonTintMode)) {mButtonDrawable = mButtonDrawable.mutate();if (mHasButtonTint) {mButtonDrawable.setTintList(mButtonTintList);}if (mHasButtonTintMode) {mButtonDrawable.setTintMode(mButtonTintMode);}// The drawable (or one of its children) may not have been// stateful before applying the tint, so let's try again.if (mButtonDrawable.isStateful()) {mButtonDrawable.setState(getDrawableState());}} }?
這個方法主要就是設置mButtonDrawable 的Tint(著色)和TintMode(著色模式),之前在構造方法,setButtonDrawable() 方法中都會調用此方法,因為這兩個屬性都是基于mButtonDrawable 來設置的,而這個兩個屬性是根據兩個Boolean 屬性mHasButtonTint 和mHasButtonTintMode 來識別的,然后為true,就表示設置。而他們兩個屬性也有setButtonTintList 和setButtonTintMode() 方法來設置兩個屬性,將兩個boolean 屬性設置為true,并且調用applyButtonTint() 來設置的。
繪制
protected void onDraw(Canvas canvas) {final Drawable buttonDrawable = mButtonDrawable;if (buttonDrawable != null) {final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;final int drawableHeight = buttonDrawable.getIntrinsicHeight();final int drawableWidth = buttonDrawable.getIntrinsicWidth();final int top;switch (verticalGravity) {case Gravity.BOTTOM:top = getHeight() - drawableHeight;break;case Gravity.CENTER_VERTICAL:top = (getHeight() - drawableHeight) / 2;break;default:top = 0;}final int bottom = top + drawableHeight;final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;final int right = isLayoutRtl() ? getWidth() : drawableWidth;buttonDrawable.setBounds(left, top, right, bottom);final Drawable background = getBackground();if (background != null) {background.setHotspotBounds(left, top, right, bottom);}}super.onDraw(canvas);if (buttonDrawable != null) {final int scrollX = mScrollX;final int scrollY = mScrollY;if (scrollX == 0 && scrollY == 0) {buttonDrawable.draw(canvas);} else {canvas.translate(scrollX, scrollY);buttonDrawable.draw(canvas);canvas.translate(-scrollX, -scrollY);}} }?
這些屬性都初始化好了,那就可以來繪制了,我們都知道自定義重寫onDraw() 方法來繪制視圖,CompoundButton 也重寫了此方法,將我們設置了各種屬性的mButtonDrawable 復制給局部變量buttonDrawable,然后根據對其方式(Gravity) 屬性來來具體繪制buttonDrawable。然后調用父類的onDraw(),最后在根據時候是滑動通過Canvas 來繪制,如果水平和垂直滑動為0,則直接繪制即可,如果不為零則需要調用translate 對canvas 的重新繪制。
設置選中(checked)狀態
public void setChecked(boolean checked) {if (mChecked != checked) {mChecked = checked;refreshDrawableState();notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);// 避免多次調用setChecked() 來多次調用回調監聽if (mBroadcasting) {return;}mBroadcasting = true;if (mOnCheckedChangeListener != null) {mOnCheckedChangeListener.onCheckedChanged(this, mChecked);}if (mOnCheckedChangeWidgetListener != null) {mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);}mBroadcasting = false; } }// 設置監狀態聽 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {mOnCheckedChangeListener = listener; }void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {mOnCheckedChangeWidgetListener = listener; }?
設置狀態,就是傳入一個Boolean 值來設置狀態,如果和初始化的mChecked 相反,才會調用,然后調用refreshDrawableState() 來刷新繪制的狀態,然后下面就是設置狀態改變的監聽,通過mBroadcasting 屬性來避免多次設置回調,每次調用mBroadcasting,如果為true,則返回。發現有兩個監聽,我們仔細看下面setXxxListener() 的方法,而下面那個setOnCheckedChangeWidgetListener() 方法不是Public,所以說不是對外開放的,我們是不能調用的,文檔中說明是僅供內部使用。因此我們想要監聽選中狀態,可以使用setOnCheckedChangeListener()。重寫onCheckedChanged(CompoundButton buttonView, boolean isChecked) 即可。對了,除了setChecked() 可以設置狀態,toggle() 每次也會setChecked() 方法,每次都會講狀態設置為相反的。還有isChecked() 方法,判斷當前的選中狀態。
狀態保存
static class SavedState extends BaseSavedState {boolean checked;SavedState(Parcelable superState) {super(superState);}private SavedState(Parcel in) {super(in);checked = (Boolean)in.readValue(null);}@Overridepublic void writeToParcel(Parcel out, int flags) {super.writeToParcel(out, flags);out.writeValue(checked);}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];}}; }@Override public Parcelable onSaveInstanceState() {Parcelable superState = super.onSaveInstanceState();SavedState ss = new SavedState(superState);ss.checked = isChecked();return ss; }@Override public void onRestoreInstanceState(Parcelable state) {SavedState ss = (SavedState) state;super.onRestoreInstanceState(ss.getSuperState());setChecked(ss.checked);requestLayout(); }?
保存狀態是自定義一個SavedState,繼承自BaseSavedState,然后Parcelable 將Boobean 類型checked 屬性序列化,判斷是否選中,在onSaveInstanceState() 中,保存,然后在onRestoreInstanceState() 獲取序列化的屬性,重新調用setChecked() 設置屬性。
總結
以上是生活随笔為你收集整理的CompoundButton 源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 出现报错Maximum call sta
- 下一篇: Three.js学习07