編輯:關於Android編程
前一段時間在做項目的時候遇到了一個問題,美工在設計的時候設計的是一個iPhone中的開關,但是都知道Android中的Switch開關和IOS中的不同,這樣就需要通過動畫來實現一個iPhone開關了。
通常我們設置界面采用的是PreferenceActivity
package me.imid.movablecheckbox; import android.os.Bundle; import android.preference.PreferenceActivity; public class MovableCheckboxActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.testpreference); } }
有關PreferenceActivity請看:http://blog.csdn.net/dawanganban/article/details/19082949
我們的基本思路是將CheckBox自定義成我們想要的樣子,然後再重寫CheckBoxPreference將自定義的CheckBox載入。
1、重寫CheckBox
package me.imid.view; import me.imid.movablecheckbox.R; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.ViewParent; import android.widget.CheckBox; public class SwitchButton extends CheckBox { private Paint mPaint; private ViewParent mParent; private Bitmap mBottom; private Bitmap mCurBtnPic; private Bitmap mBtnPressed; private Bitmap mBtnNormal; private Bitmap mFrame; private Bitmap mMask; private RectF mSaveLayerRectF; private PorterDuffXfermode mXfermode; private float mFirstDownY; // 首次按下的Y private float mFirstDownX; // 首次按下的X private float mRealPos; // 圖片的繪制位置 private float mBtnPos; // 按鈕的位置 private float mBtnOnPos; // 開關打開的位置 private float mBtnOffPos; // 開關關閉的位置 private float mMaskWidth; private float mMaskHeight; private float mBtnWidth; private float mBtnInitPos; private int mClickTimeout; private int mTouchSlop; private final int MAX_ALPHA = 255; private int mAlpha = MAX_ALPHA; private boolean mChecked = false; private boolean mBroadcasting; private boolean mTurningOn; private PerformClick mPerformClick; private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener; private boolean mAnimating; private final float VELOCITY = 350; private float mVelocity; private final float EXTENDED_OFFSET_Y = 15; private float mExtendOffsetY; // Y軸方向擴大的區域,增大點擊區域 private float mAnimationPosition; private float mAnimatedVelocity; public SwitchButton(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.checkboxStyle); } public SwitchButton(Context context) { this(context, null); } public SwitchButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context); } private void initView(Context context) { mPaint = new Paint(); mPaint.setColor(Color.WHITE); Resources resources = context.getResources(); // get viewConfiguration mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // get Bitmap mBottom = BitmapFactory.decodeResource(resources, R.drawable.bottom); mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.btn_pressed); mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.btn_unpressed); mFrame = BitmapFactory.decodeResource(resources, R.drawable.frame); mMask = BitmapFactory.decodeResource(resources, R.drawable.mask); mCurBtnPic = mBtnNormal; mBtnWidth = mBtnPressed.getWidth(); mMaskWidth = mMask.getWidth(); mMaskHeight = mMask.getHeight(); mBtnOffPos = mBtnWidth / 2; mBtnOnPos = mMaskWidth - mBtnWidth / 2; mBtnPos = mChecked ? mBtnOnPos : mBtnOffPos; mRealPos = getRealPos(mBtnPos); final float density = getResources().getDisplayMetrics().density; mVelocity = (int) (VELOCITY * density + 0.5f); mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f); mSaveLayerRectF = new RectF(0, mExtendOffsetY, mMask.getWidth(), mMask.getHeight() + mExtendOffsetY); mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); } @Override public void setEnabled(boolean enabled) { mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 2; super.setEnabled(enabled); } public boolean isChecked() { return mChecked; } public void toggle() { setChecked(!mChecked); } /** * 內部調用此方法設置checked狀態,此方法會延遲執行各種回調函數,保證動畫的流暢度 * * @param checked */ private void setCheckedDelayed(final boolean checked) { this.postDelayed(new Runnable() { @Override public void run() { setChecked(checked); } }, 10); } /** *2、新建一個布局文件preference_widget_checkbox.xml* Changes the checked state of this button. *
* * @param checked true to check the button, false to uncheck it */ public void setChecked(boolean checked) { if (mChecked != checked) { mChecked = checked; mBtnPos = checked ? mBtnOnPos : mBtnOffPos; mRealPos = getRealPos(mBtnPos); invalidate(); // Avoid infinite recursions if setChecked() is called from a // listener if (mBroadcasting) { return; } mBroadcasting = true; if (mOnCheckedChangeListener != null) { mOnCheckedChangeListener.onCheckedChanged(SwitchButton.this, mChecked); } if (mOnCheckedChangeWidgetListener != null) { mOnCheckedChangeWidgetListener.onCheckedChanged(SwitchButton.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; } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); float x = event.getX(); float y = event.getY(); float deltaX = Math.abs(x - mFirstDownX); float deltaY = Math.abs(y - mFirstDownY); switch (action) { case MotionEvent.ACTION_DOWN: attemptClaimDrag(); mFirstDownX = x; mFirstDownY = y; mCurBtnPic = mBtnPressed; mBtnInitPos = mChecked ? mBtnOnPos : mBtnOffPos; break; case MotionEvent.ACTION_MOVE: float time = event.getEventTime() - event.getDownTime(); mBtnPos = mBtnInitPos + event.getX() - mFirstDownX; if (mBtnPos >= mBtnOffPos) { mBtnPos = mBtnOffPos; } if (mBtnPos <= mBtnOnPos) { mBtnPos = mBtnOnPos; } mTurningOn = mBtnPos > (mBtnOffPos - mBtnOnPos) / 2 + mBtnOnPos; mRealPos = getRealPos(mBtnPos); break; case MotionEvent.ACTION_UP: mCurBtnPic = mBtnNormal; time = event.getEventTime() - event.getDownTime(); if (deltaY < mTouchSlop && deltaX < mTouchSlop && time < mClickTimeout) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } else { startAnimation(!mTurningOn); } break; } invalidate(); return isEnabled(); } private final class PerformClick implements Runnable { public void run() { performClick(); } } @Override public boolean performClick() { startAnimation(!mChecked); return true; } /** * Tries to claim the user's drag motion, and requests disallowing any * ancestors from stealing events in the drag. */ private void attemptClaimDrag() { mParent = getParent(); if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(true); } } /** * 將btnPos轉換成RealPos * * @param btnPos * @return */ private float getRealPos(float btnPos) { return btnPos - mBtnWidth / 2; } @Override protected void onDraw(Canvas canvas) { canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); // 繪制蒙板 canvas.drawBitmap(mMask, 0, mExtendOffsetY, mPaint); mPaint.setXfermode(mXfermode); // 繪制底部圖片 canvas.drawBitmap(mBottom, mRealPos, mExtendOffsetY, mPaint); mPaint.setXfermode(null); // 繪制邊框 canvas.drawBitmap(mFrame, 0, mExtendOffsetY, mPaint); // 繪制按鈕 canvas.drawBitmap(mCurBtnPic, mRealPos, mExtendOffsetY, mPaint); canvas.restore(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension((int) mMaskWidth, (int) (mMaskHeight + 2 * mExtendOffsetY)); } private void startAnimation(boolean turnOn) { mAnimating = true; mAnimatedVelocity = turnOn ? -mVelocity : mVelocity; mAnimationPosition = mBtnPos; new SwitchAnimation().run(); } private void stopAnimation() { mAnimating = false; } private final class SwitchAnimation implements Runnable { @Override public void run() { if (!mAnimating) { return; } doAnimation(); FrameAnimationController.requestAnimationFrame(this); } } private void doAnimation() { mAnimationPosition += mAnimatedVelocity * FrameAnimationController.ANIMATION_FRAME_DURATION / 1000; if (mAnimationPosition <= mBtnOnPos) { stopAnimation(); mAnimationPosition = mBtnOnPos; setCheckedDelayed(true); } else if (mAnimationPosition >= mBtnOffPos) { stopAnimation(); mAnimationPosition = mBtnOffPos; setCheckedDelayed(false); } moveView(mAnimationPosition); } private void moveView(float position) { mBtnPos = position; mRealPos = getRealPos(mBtnPos); invalidate(); } }
3、重寫CheckBoxPreference並通過Inflater加載布局文件,同時屏蔽原有點擊事件
package me.imid.preference; import me.imid.movablecheckbox.R; import me.imid.view.SwitchButton; import android.app.Service; import android.content.Context; import android.preference.PreferenceActivity; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Checkable; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; public class CheckBoxPreference extends android.preference.CheckBoxPreference { private Context mContext; private int mLayoutResId = R.layout.preference; private int mWidgetLayoutResId = R.layout.preference_widget_checkbox; private boolean mShouldDisableView = true; private CharSequence mSummaryOn; private CharSequence mSummaryOff; private boolean mSendAccessibilityEventViewClickedType; private AccessibilityManager mAccessibilityManager; public CheckBoxPreference(Context context, AttributeSet attrset, int defStyle) { super(context, attrset); mContext = context; mSummaryOn = getSummaryOn(); mSummaryOff = getSummaryOff(); mAccessibilityManager = (AccessibilityManager) mContext .getSystemService(Service.ACCESSIBILITY_SERVICE); } public CheckBoxPreference(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.checkBoxPreferenceStyle); } public CheckBoxPreference(Context context) { this(context, null); } /** * Creates the View to be shown for this Preference in the * {@link PreferenceActivity}. The default behavior is to inflate the main * layout of this Preference (see {@link #setLayoutResource(int)}. If * changing this behavior, please specify a {@link ViewGroup} with ID * {@link android.R.id#widget_frame}. *4、在res/xml下新建選項設置布局文件* Make sure to call through to the superclass's implementation. * * @param parent * The parent that this View will eventually be attached to. * @return The View that displays this Preference. * @see #onBindView(View) */ protected View onCreateView(ViewGroup parent) { final LayoutInflater layoutInflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View layout = layoutInflater.inflate(mLayoutResId, parent, false); if (mWidgetLayoutResId != 0) { final ViewGroup widgetFrame = (ViewGroup) layout .findViewById(R.id.widget_frame); layoutInflater.inflate(mWidgetLayoutResId, widgetFrame); } return layout; } @Override protected void onBindView(View view) { // 屏蔽item點擊事件 view.setClickable(false); TextView textView = (TextView) view.findViewById(R.id.title); if (textView != null) { textView.setText(getTitle()); } textView = (TextView) view.findViewById(R.id.summary); if (textView != null) { final CharSequence summary = getSummary(); if (!TextUtils.isEmpty(summary)) { if (textView.getVisibility() != View.VISIBLE) { textView.setVisibility(View.VISIBLE); } textView.setText(getSummary()); } else { if (textView.getVisibility() != View.GONE) { textView.setVisibility(View.GONE); } } } if (mShouldDisableView) { setEnabledStateOnViews(view, isEnabled()); } View checkboxView = view.findViewById(R.id.checkbox); if (checkboxView != null && checkboxView instanceof Checkable) { ((Checkable) checkboxView).setChecked(isChecked()); SwitchButton switchButton = (SwitchButton) checkboxView; switchButton .setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub mSendAccessibilityEventViewClickedType = true; if (!callChangeListener(isChecked)) { return; } setChecked(isChecked); } }); // send an event to announce the value change of the CheckBox and is // done here // because clicking a preference does not immediately change the // checked state // for example when enabling the WiFi if (mSendAccessibilityEventViewClickedType && mAccessibilityManager.isEnabled() && checkboxView.isEnabled()) { mSendAccessibilityEventViewClickedType = false; int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED; checkboxView.sendAccessibilityEventUnchecked(AccessibilityEvent .obtain(eventType)); } } // Sync the summary view TextView summaryView = (TextView) view.findViewById(R.id.summary); if (summaryView != null) { boolean useDefaultSummary = true; if (isChecked() && mSummaryOn != null) { summaryView.setText(mSummaryOn); useDefaultSummary = false; } else if (!isChecked() && mSummaryOff != null) { summaryView.setText(mSummaryOff); useDefaultSummary = false; } if (useDefaultSummary) { final CharSequence summary = getSummary(); if (summary != null) { summaryView.setText(summary); useDefaultSummary = false; } } int newVisibility = View.GONE; if (!useDefaultSummary) { // Someone has written to it newVisibility = View.VISIBLE; } if (newVisibility != summaryView.getVisibility()) { summaryView.setVisibility(newVisibility); } } } /** * Makes sure the view (and any children) get the enabled state changed. */ private void setEnabledStateOnViews(View v, boolean enabled) { v.setEnabled(enabled); if (v instanceof ViewGroup) { final ViewGroup vg = (ViewGroup) v; for (int i = vg.getChildCount() - 1; i >= 0; i--) { setEnabledStateOnViews(vg.getChildAt(i), enabled); } } } }
運行結果:
package xiaosi.location; import android.app.Activity; import android.os.Bund
輔助功能定義對於那些由於視力、聽力或其它身體原因導致不能方便使用Android智能手機的用戶,Android提供了Accessibility功能和服務幫助這些用戶更加簡單
----下載JDK(Java Dev Kit) 官方下載:http://www.oracle.com/technetwork/java/javase/download
GridView用於在界面上按行、列分布顯示多個組件。GridView和ListView有共同父類:AbsListView。GridView與Lis