您的位置:首页 > 移动开发 > Android开发

android仿ios开关按钮

2014-02-27 21:15 453 查看
前一段时间在做项目的时候遇到了一个问题,美工在设计的时候设计的是一个iPhone中的开关,但是都知道Android中的Switch开关和IOS中的不同,这样就需要通过动画来实现一个iPhone开关了。

通常我们设置界面采用的是PreferenceActivity

[html] view
plaincopyprint?





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请看:/article/1533542.html

我们的基本思路是将CheckBox自定义成我们想要的样子,然后再重写CheckBoxPreference将自定义的CheckBox载入。

1、重写CheckBox

[java] view
plaincopyprint?





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);

}



/**

* <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;



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_S***E_FLAG

| Canvas.CLIP_S***E_FLAG | Canvas.HAS_ALPHA_LAYER_S***E_FLAG

| Canvas.FULL_COLOR_LAYER_S***E_FLAG | Canvas.CLIP_TO_LAYER_S***E_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();

}

}

2、新建一个布局文件preference_widget_checkbox.xml

[html] view
plaincopyprint?





<?xml version="1.0" encoding="utf-8"?>

<me.imid.view.SwitchButton xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/checkbox"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="right|center" />

3、重写CheckBoxPreference并通过Inflater加载布局文件,同时屏蔽原有点击事件

[java] view
plaincopyprint?





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}.

* <p>

* 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);

}

}

}



}

4、在res/xml下新建选项设置布局文件

[html] view
plaincopyprint?





<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >



<me.imid.preference.CheckBoxPreference

android:defaultValue="true"

android:enabled="false"

android:summary="summary"

android:title="MyCheckbox(disabled)" />

<me.imid.preference.CheckBoxPreference

android:defaultValue="true"

android:dependency="checkbox"

android:summaryOff="off"

android:summaryOn="on"

android:title="MyCheckbox(enabled)" />

<me.imid.preference.CheckBoxPreference

android:defaultValue="false"

android:key="checkbox"

android:summaryOff="off"

android:summaryOn="on"

android:title="MyCheckbox(enabled)" />



<CheckBoxPreference

android:defaultValue="true"

android:enabled="false"

android:summaryOff="off"

android:summaryOn="on"

android:title="defalt checkbox(disabled)" />

<CheckBoxPreference

android:defaultValue="true"

android:dependency="checkbox1"

android:summaryOff="off"

android:summaryOn="on"

android:title="defalt checkbox(enabled)" />

<CheckBoxPreference

android:defaultValue="false"

android:key="checkbox1"

android:summaryOff="off"

android:summaryOn="on"

android:title="defalt checkbox(enabled)" />



</PreferenceScreen>

运行结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: