@RemoteView public class Button extends TextView { public Button(Context context) { this(context, null); } public Button(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.buttonStyle); } public Button(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(Button. class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(Button. class.getName()); } }
它是一个抽象类,而实现这个类的控件正是CheckBox, RadioButton, Switch, ToggleButton这四个,所以先重点说一下它。
源代码例如以下:
/** * <p> * A button with two states, checked and unchecked. When the button is pressed * or clicked, the state changes automatically. * </p> * * <p><strong>XML attributes </strong></p> * <p> * See {@link android.R.styleable#CompoundButton * CompoundButton Attributes}, {@link android.R.styleable#Button Button * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link * android.R.styleable #View View Attributes} * </p> */ public abstract class CompoundButton extends Button implements Checkable { private boolean mChecked ; private int mButtonResource ; private boolean mBroadcasting ; private Drawable mButtonDrawable; private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener ; private static final int[] CHECKED_STATE_SET = { R.attr.state_checked }; public CompoundButton(Context context) { this(context, null); } public CompoundButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CompoundButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0); Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); if (d != null ) { setButtonDrawable(d); } boolean checked = a .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false); setChecked(checked); a.recycle(); } public void toggle() { setChecked(! mChecked); } @Override public boolean performClick() { /* * XXX: These are tiny, need some surrounding 'expanded touch area', * which will need to be implemented in Button if we only override * performClick() */ /* When clicked, toggle the state */ toggle(); return super .performClick(); } @ViewDebug.ExportedProperty public boolean isChecked() { return mChecked ; } /** * <p>Changes the checked state of this button.</p> * * @param checked true to check the button, false to uncheck it */ public void setChecked(boolean checked) { if (mChecked != checked) { mChecked = checked; 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); } if (mOnCheckedChangeWidgetListener != null) { mOnCheckedChangeWidgetListener .onCheckedChanged(this, mChecked); } mBroadcasting = false ; } } /** * Register a callback to be invoked when the checked state of this button * changes. * * @param listener the callback to call on checked state change */ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { mOnCheckedChangeListener = listener; } /** * Register a callback to be invoked when the checked state of this button * changes. This callback is used for internal purpose only. * * @param listener the callback to call on checked state change * @hide */ void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) { mOnCheckedChangeWidgetListener = listener; } /** * Interface definition for a callback to be invoked when the checked state * of a compound button changed. */ public static interface OnCheckedChangeListener { /** * Called when the checked state of a compound button has changed. * * @param buttonView The compound button view whose state has changed. * @param isChecked The new checked state of buttonView. */ void onCheckedChanged(CompoundButton buttonView, boolean isChecked); } /** * Set the background to a given Drawable, identified by its resource id. * * @param resid the resource id of the drawable to use as the background */ public void setButtonDrawable(int resid) { if (resid != 0 && resid == mButtonResource ) { return; } mButtonResource = resid; Drawable d = null; if (mButtonResource != 0) { d = getResources().getDrawable(mButtonResource ); } setButtonDrawable(d); } /** * Set the background to a given Drawable * * @param d The Drawable to use as the background */ public void setButtonDrawable(Drawable d) { if (d != null ) { if (mButtonDrawable != null) { mButtonDrawable.setCallback(null); unscheduleDrawable( mButtonDrawable); } d.setCallback( this); d.setVisible(getVisibility() == VISIBLE, false); mButtonDrawable = d; setMinHeight(mButtonDrawable .getIntrinsicHeight()); } refreshDrawableState(); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(CompoundButton.class .getName()); event.setChecked( mChecked); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(CompoundButton.class .getName()); info.setCheckable( true); info.setChecked( mChecked); } @Override public int getCompoundPaddingLeft() { int padding = super.getCompoundPaddingLeft(); if (!isLayoutRtl()) { final Drawable buttonDrawable = mButtonDrawable; if (buttonDrawable != null) { padding += buttonDrawable.getIntrinsicWidth(); } } return padding; } @Override public int getCompoundPaddingRight() { int padding = super.getCompoundPaddingRight(); if (isLayoutRtl()) { final Drawable buttonDrawable = mButtonDrawable; if (buttonDrawable != null) { padding += buttonDrawable.getIntrinsicWidth(); } } return padding; } /** * @hide */ @Override public int getHorizontalOffsetForDrawables() { final Drawable buttonDrawable = mButtonDrawable ; return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0; } @Override protected void onDraw(Canvas canvas) { super.onDraw(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(); int top = 0; switch (verticalGravity) { case Gravity.BOTTOM : top = getHeight() - drawableHeight; break; case Gravity.CENTER_VERTICAL : top = (getHeight() - drawableHeight) / 2; break; } int bottom = top + drawableHeight; int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; int right = isLayoutRtl() ?先从构造方法開始,在构造方法中。getWidth() : drawableWidth; buttonDrawable.setBounds(left, top, right, bottom); buttonDrawable.draw(canvas); } } @Override protected int[] onCreateDrawableState(int extraSpace) { final int [] drawableState = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mButtonDrawable != null) { int[] myDrawableState = getDrawableState(); // Set the state of the Drawable mButtonDrawable.setState(myDrawableState); invalidate(); } } @Override protected boolean verifyDrawable(Drawable who) { return super .verifyDrawable(who) || who == mButtonDrawable; } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState(); } static class SavedState extends BaseSavedState { boolean checked ; /** * Constructor called from {@link CompoundButton#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); checked = (Boolean)in.readValue( null); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeValue( checked); } @Override public String toString() { return "CompoundButton.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + 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() { // Force our ancestor class to save its state setFreezesText( true); 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(); } }
public CompoundButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0); Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); if (d != null ) { setButtonDrawable(d); } boolean checked = a .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false); setChecked(checked); a.recycle(); }先是从attrs中读取定义的属性。一个是Drawable用于设置背景。一个是布尔类型变量用于推断是否check过。
设置背景使用的是setButtonDrawable()方法,代码例如以下:
/** * Set the background to a given Drawable * * @param d The Drawable to use as the background */ public void setButtonDrawable(Drawable d) { if (d != null ) { if (mButtonDrawable != null) { mButtonDrawable.setCallback(null); unscheduleDrawable( mButtonDrawable); } d.setCallback( this); d.setVisible(getVisibility() == VISIBLE, false); mButtonDrawable = d; setMinHeight(mButtonDrawable .getIntrinsicHeight()); } refreshDrawableState(); }这种方法写的就比較完好。能够作为一个学习的典范。
首先推断传递过来的Drawable是否为空,假设不为空而且默认的Drawable也不为空,那么取消默认Drawable的callback。然后调用unscheduleDrawable方法。这种方法代码例如以下:
/** * Unschedule any events associated with the given Drawable. This can be * used when selecting a new Drawable into a view, so that the previous * one is completely unscheduled. * * @param who The Drawable to unschedule. * * @see #drawableStateChanged */ public void unscheduleDrawable(Drawable who) { if (mAttachInfo != null && who != null) { mAttachInfo.mViewRootImpl .mChoreographer.removeCallbacks( Choreographer.CALLBACK_ANIMATION, null, who); } }从方法凝视中能够看出它的用途,正是更换Drawable时候使用的。
接下来開始又一次设置Drawable。包含回调、可见性、最小高度。最后调用refreshDrawableState()方法,这个是View类的方法。用于更新Drawable状态。
/** * <p>Changes the checked state of this button.</p> * * @param checked true to check the button, false to uncheck it */ public void setChecked( boolean checked) { if (mChecked != checked) { mChecked = checked; 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); } if (mOnCheckedChangeWidgetListener != null) { mOnCheckedChangeWidgetListener .onCheckedChanged(this, mChecked); } mBroadcasting = false ; } }
/** * Interface definition for a callback to be invoked when the checked state * of a compound button changed. */ public static interface OnCheckedChangeListener { /** * Called when the checked state of a compound button has changed. * * @param buttonView The compound button view whose state has changed. * @param isChecked The new checked state of buttonView. */ void onCheckedChanged(CompoundButton buttonView, boolean isChecked); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(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(); int top = 0; switch (verticalGravity) { case Gravity.BOTTOM : top = getHeight() - drawableHeight; break; case Gravity.CENTER_VERTICAL : top = (getHeight() - drawableHeight) / 2; break; } int bottom = top + drawableHeight; int left = isLayoutRtl() ?getWidth() - drawableWidth : 0; int right = isLayoutRtl() ? getWidth() : drawableWidth; buttonDrawable.setBounds(left, top, right, bottom); buttonDrawable.draw(canvas); } }
确定完后就能够画出来了。可是,CompoundButton是一个抽象类。并不能直接使用。那看一下它的子类是怎样实现的:
public class CheckBox extends CompoundButton { public CheckBox(Context context) { this(context, null); } public CheckBox(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.checkboxStyle); } public CheckBox(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(CheckBox. class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(CheckBox. class.getName()); } }
再来看一下RadioButton,
public class RadioButton extends CompoundButton { public RadioButton(Context context) { this(context, null); } public RadioButton(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.radioButtonStyle); } public RadioButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * {@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) if (!isChecked()) { super.toggle(); } } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(RadioButton. class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(RadioButton. class.getName()); } }和CheckBox实现差点儿相同,差别在于多重写了一个方法。用于防止按钮被反复点击。另外还有ToggleButton以及Switch,前者实现也比較简单,后者略微麻烦了一些,感兴趣能够自己分析。
/** * */ package com.kince.slidebutton; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * @author kince * @category 左右手势滑动button * @serial 1.0.0 * @since 2014.5.17 * @see http://blog.csdn.net/wangjinyu501 * */ public class SlideButton extends View { private Bitmap slideBitMap;// 滑动图片 private Bitmap switchBitMap;// 背景图片 private int slideBitMapWidth;// 滑动图片宽度 private int switchBitMapWidth;// 背景图片宽度 private int switchBitMapHeight;// 背景图片高度 private boolean currentState;// 开关状态 private boolean isSliding = false; // 是否正在滑动中 private int currentX; // 当前开关的位置 private OnToggleStateChangedListener mChangedListener;// 回调接口 /** * @param context * 在java代码中直接调用使用此构造方法 */ public SlideButton(Context context) { this(context, null); // TODO Auto-generated constructor stub } /** * @param context * @param attrs * 在xml中使用要用到这种方法 */ public SlideButton(Context context, AttributeSet attrs) { this(context, attrs, 0); // TODO Auto-generated constructor stub } /** * @param context * @param attrs * @param defStyleAttr * 指定一个样式 */ public SlideButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initBitmap(); } /** * @category 载入背景图片以及开关图片 然后获取各自的宽高 * */ private void initBitmap() { // TODO Auto-generated method stub slideBitMap = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background); switchBitMap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slideBitMapWidth = slideBitMap.getWidth(); switchBitMapWidth = switchBitMap.getWidth(); switchBitMapHeight = switchBitMap.getHeight(); Log.i("switchBitMapWidth", switchBitMapWidth + ""); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(switchBitMapWidth, switchBitMapHeight);// 设置控件的宽高 } @Override protected void onDraw(Canvas canvas) { // 绘制button背景图片 canvas.drawBitmap(switchBitMap, 0, 0, null); // 绘制滑动开关 if (isSliding) {// 假设当前状态是滑动中 则动态绘制开关 int dis = currentX - slideBitMapWidth / 2; if (dis < 0) { dis = 0; } else if (dis > switchBitMapWidth - slideBitMapWidth) { dis = switchBitMapWidth - slideBitMapWidth; } canvas.drawBitmap(slideBitMap, dis, 0, null); } else { if (currentState) { // 绘制开关为开的状态 canvas.drawBitmap(slideBitMap, switchBitMapWidth - slideBitMapWidth, 0, null); } else { // 绘制开关为关的状态 canvas.drawBitmap(slideBitMap, 0, 0, null); } } super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { // 手势识别 推断滑动方向 int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: isSliding = true; currentX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: currentX = (int) event.getX(); Log.i("currentX", currentX + ""); break; case MotionEvent.ACTION_UP: isSliding = false; int bgCenter = switchBitMapWidth / 2; boolean state = currentX > bgCenter; // 改变后的状态 if (state != currentState && mChangedListener != null) {// 加入回调 mChangedListener.onToggleStateChanged(state); } currentState = state; break; default: break; } invalidate(); return true; } public OnToggleStateChangedListener getmChangedListener() { return mChangedListener; } public void setmChangedListener( OnToggleStateChangedListener mChangedListener) { this.mChangedListener = mChangedListener; } public boolean isToggleState() { return currentState; } public void setToggleState(boolean currentState) { this.currentState = currentState; } }回调接口,
package com.kince.slidebutton; /** * @author kince * */ public interface OnToggleStateChangedListener { /** * @category * @param state */ public void onToggleStateChanged(boolean state); }Activity代码,
package com.kince.slidebutton; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import android.os.Build; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, new PlaceholderFragment()).commit(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment implements OnToggleStateChangedListener { private SlideButton slidebutton; public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); slidebutton = (SlideButton) rootView.findViewById(R.id.slidebutton1); // 设置一下开关的状态 slidebutton.setToggleState(true); // 设置开关的状态为打开 slidebutton.setmChangedListener(this); return rootView; } @Override public void onToggleStateChanged(boolean state) { // TODO Auto-generated method stub FragmentActivity activity = getActivity(); if (state) { Toast.makeText(activity, "开关打开", 0).show(); } else { Toast.makeText(activity, "开关关闭", 0).show(); } } } }未完待续。
版权声明:本文博客原创文章,博客,未经同意,不得转载。