天天看點

android仿ios開關按鈕

前一段時間在做項目的時候遇到了一個問題,美工在設計的時候設計的是一個iphone中的開關,但是都知道android中的switch開關和ios中的不同,這樣就需要通過動畫來實作一個iphone開關了。

通常我們設定界面采用的是preferenceactivity

android仿ios開關按鈕

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

android仿ios開關按鈕

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

android仿ios開關按鈕

<?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加載布局檔案,同時屏蔽原有點選事件

android仿ios開關按鈕

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下建立選項設定布局檔案

android仿ios開關按鈕

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

運作結果:

android仿ios開關按鈕

繼續閱讀