前一段時間在做項目的時候遇到了一個問題,美工在設計的時候設計的是一個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);
}
}
我們的基本思路是将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);
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;
* changes. this callback is used for internal purpose only.
* @hide
void setoncheckedchangewidgetlistener(oncheckedchangelistener listener) {
moncheckedchangewidgetlistener = listener;
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);
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);
invalidate();
return isenabled();
private final class performclick implements runnable {
public void run() {
performclick();
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;
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();
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
if (!manimating) {
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) {
manimationposition = mbtnoffpos;
setcheckeddelayed(false);
moveview(manimationposition);
private void moveview(float position) {
mbtnpos = position;
2、建立一個布局檔案preference_widget_checkbox.xml
<?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加載布局檔案,同時屏蔽原有點選事件
package me.imid.preference;
import me.imid.view.switchbutton;
import android.app.service;
import android.text.textutils;
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) {
* 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}.
* 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;
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);
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);
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下建立選項設定布局檔案
<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)" />
android:dependency="checkbox"
android:summaryoff="off"
android:summaryon="on"
android:title="mycheckbox(enabled)" />
android:defaultvalue="false"
android:key="checkbox"
<checkboxpreference
android:title="defalt checkbox(disabled)" />
android:dependency="checkbox1"
android:title="defalt checkbox(enabled)" />
android:key="checkbox1"
</preferencescreen>
運作結果: