[view] 07_滑动开关SlipToggle

Android 4.0

滑动开关
一、效果:开启时:

关闭时:


二、步骤:
1、写一个类MyToggle继承View, 
两个构造方法(一个,二个参数的)
2、提供一个对外的方法,用来设置setBkgRes背景图片资源,开启的背景
bkgSwitchOn,关闭的背景 bkgSwitchOff和滑动块的背景图片bkgBtnSlip以及记录滑动块处于开启和关闭时的位置
a)开启:
   滑动块左边坐标:
bkgSwitchOn的宽度减去bkgBtnSlip的宽度
   滑动块上面坐标:0
   
滑动块右面坐标:bkgSwitchOn的宽度
   滑动块下面坐标:bkgSwitchOn的高度
a)关闭:
   滑动块左边坐标:0

   滑动块上面坐标:0
   
滑动块右面坐标:获取滑块的宽度
   滑动块下面坐标:bkgSwitchOn的高度

3、提供一个方法,用来设置开关的状态
setToggleState(boolean status),并用一个成员变量toggleStateOn记住当前开关是否开启

4、提供一个监听器,来监听滑动开关状态的变化,以便于来响应不同的事件
setOnToggleStatusListener(OnToggleStatusListener listener),并用成员变量 toggleStatusListener记录该listener和 isToggleStatusListenerOn记录是否注册了该监听器
接口类
OnToggleStatusListener 提供一个方法, abstract void onToggleStatus(boolean status)用来根据是否开启来处理不同的事件,

 5、定义一个初始化 init()的方法,在对象创建的时候,就注册一个触摸 setOnTouchListener事件,可以用来触摸滑动块,该方法在构造器里面就调用,类实现OnTouchListener接口,实现 onTouch()方法。

6、实现触摸时的逻辑
a) ACTION_DOWN 手按下
记住当前的位置: currentX 
记住当前正在滑行: isSlipping   
b) ACTION_MOVE   拖拽
移动过程中,更新滑行的x坐标currentX  
c) ACTION_UP 松手
将滑行的状态设置为false
如果当前 currentX小于背景bkgSwitchOn的一半宽度,那么就是关闭状态,toggleStateOn  =  false; 
如果当前
 currentX大于背景bkgSwitchOn的一半宽度,那么就是开启状态, toggleStateOn  = true; 
定义一个
proToggleState,记录松手前的状态,如果设置了监听器且开关状态变换了,那么执行监听器的方法,
同时将proToggleState设置为当前松手时这个状态
d) invalidate() ,重新调用onDraw()方法

7、测量onMesure()
设置
bkgSwitchOn的宽度和高度

8、绘制背景bkgSwitchOn、bkgSwitchOff和滑块
a)绘制背景
如果 currentX 大于背景图二分之一,那么绘制 bkgSwitchOn
如果currentX 小于背景图的二分之一,那么绘制bkgSwitchOff
b)绘制滑块
1)滑块处于滑动状态,
 如果当前currentX 大于背景图宽度,那么滑块最左边 left_slip 等于背景图宽度减去滑块宽度
 否则的话, left_slip   =  currentX 减去滑块一个,可以保证currentX 总是在滑块中间
2)滑动处于静止状态
如果开关处于开启状态,那么滑块left_slip  =  背景图减去滑块宽度
如果开关处于关闭状态,那么滑块left_slip   = 0;
3)健壮性判断
如果left_slip小于0,超出左边,那么left_slip  = 0;
如果left_slip大于背景图宽度减去滑块宽度,超出右边,那么left_slip=背景图宽度减去滑块宽度
4)开始绘制滑块。
核心代码:
a)布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <cn.zengfansheng.sliptoggle.SlipToggle
        android:id="@+id/toggle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >
    </cn.zengfansheng.sliptoggle.SlipToggle>
</RelativeLayout>  
b)代码1:
package cn.zengfansheng.sliptoggle;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import cn.zengfansheng.sliptoggle.SlipToggle.OnToggleStatusListener;
public class MainActivity extends Activity {
    private SlipToggle toggle;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toggle = (SlipToggle) this.findViewById(R.id.toggle);
        
        //设置资源文件
        toggle.setBkgRes(R.drawable.bkg_switch, R.drawable.bkg_switch, R.drawable.btn_slip);
        
        // 设置开关为true状态
        toggle.setToggleState(false);
        // 设置监听器
        toggle.setOnToggleStatusListener(new OnToggleStatusListener() {
            @Override
            public void onToggleStatus(boolean toggleState) {
                if (toggleState) {
                    Toast.makeText(getApplicationContext(), "开关开启", 0).show();
                }else {
                    Toast.makeText(getApplicationContext(), "开关关闭", 0).show();
                }
            }
        });
    }
}
c)自定义控件-滑动开关
package cn.zengfansheng.sliptoggle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
/**
 * 滑动开关
 * 
 * @author hacket
 */
public class SlipToggle extends View implements OnTouchListener {
    private Bitmap bkg_btnSlip;
    private Bitmap bkg_switchOn;
    private Bitmap bkg_switchOff;
    private Rect btnSlip_switch_on;// 开关开启时滑块坐标
    private Rect btnSlip_switch_off;// 开关关闭时滑块坐标
    
    private boolean isToggleStateOn;// 设置开关的状态
    
    private OnToggleStatusListener toggleStatusListener;// 开关状态监听器
    private boolean isToggleStatusListenerOn;// 是否开启的开关状态监听
    private float slipBtnCurrentX;// 滑块当前的的位置
    private boolean isSlipping;// 是否滑块正在滑行
    private boolean proToggleState;// 前一个开关的状态,默认为false
    // TODO 1、继承View,两个构造方法
    public SlipToggle(Context context) {
        super(context);
        init();
    }
    public SlipToggle(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    // TODO 5、初始化触摸监听事件,在对象创建时注册开关触摸事件
    public void init() {
        setOnTouchListener(this);
    }
    // 6、实现触摸时onTouch的逻辑
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:// 触摸按下
            slipBtnCurrentX = event.getX();// 滑块当前的的位置
            isSlipping = true;// 是否滑块正在滑行
            break;
        case MotionEvent.ACTION_MOVE:// 触摸移动
            
            slipBtnCurrentX = event.getX();
            
            break;
        case MotionEvent.ACTION_UP:// 触摸松手
            //是否滑块在滑行
            isSlipping = false;
            // 滑块的位置
            if (slipBtnCurrentX < bkg_switchOn.getWidth() / 2) {// 当前位置小于背景一半,关闭
                isToggleStateOn = false;
            } else {
                isToggleStateOn = true;
            }
            
            // 如果设置了开关状态变化监听器,且和前一个状态不同
            if (isToggleStatusListenerOn && isToggleStateOn != proToggleState) {
                proToggleState = isToggleStateOn;// 前一个状态更改为现在的状态
                toggleStatusListener.onToggleStatus(isToggleStateOn);
            }
            break;
        }
        invalidate();
        return true;
    }
    // TODO 7、测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(bkg_switchOn.getWidth(), bkg_switchOn.getHeight());
        setMeasuredDimension(bkg_switchOn.getWidth(), bkg_switchOn.getHeight());
        // FIXME 难点:必须在onMeasure(int,int)调用该方法
    }
    // TODO 8、绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 8-1)绘制背景
        if (isToggleStateOn) {// a)开关开启
            canvas.drawBitmap(bkg_switchOnnew Matrix(), null);
        } else {// b)开关关闭
            canvas.drawBitmap(bkg_switchOffnew Matrix(), null);
        }
        // 8-2)绘制滑块
        float left_slip = 0;// 滑块最左边位置
        if (isSlipping) {// FIXME 难点 a)滑块处于滑动状态,当前状态减去滑块的二分之一,保证每次都在滑块中
            left_slip = slipBtnCurrentX - bkg_btnSlip.getWidth() / 2;
        } else {// b)滑块处于静止状态
            if (isToggleStateOn) {// 如果开关为开启状态
                left_slip = bkg_switchOn.getWidth() - bkg_btnSlip.getWidth();
            } else {// 开关为关闭状态
                left_slip = 0;
            }
        }
        // c)健壮性判断
        if (left_slip<0) {//滑块超出最左边
            left_slip = 0;
        }else if(left_slip>(bkg_switchOn.getWidth()-bkg_btnSlip.getWidth())){//滑块超出最右边
            left_slip = bkg_switchOn.getWidth() - bkg_btnSlip.getWidth();
        }
        
        // d)开始绘制滑块
        canvas.drawBitmap(bkg_btnSlip, left_slip, 0, null);
    }
    // TODO 2、对外提供一个设置资源的方法
    public void setBkgRes(int bkgSwitchOn, int bkgSwitchOff, int bkgBtnSlip) {
        
         bkg_btnSlip = BitmapFactory.decodeResource(getResources(), R.drawable.btn_slip);
         bkg_switchOn = BitmapFactory.decodeResource(getResources(), R.drawable.bkg_switch);
         bkg_switchOff = BitmapFactory.decodeResource(getResources(), R.drawable.bkg_switch);
        
        // 1、开关开启时滑块坐标
        btnSlip_switch_on = new Rect(bkg_switchOn.getWidth()-bkg_btnSlip.getWidth(),0,bkg_switchOn.getWidth(),bkg_switchOn.getHeight());
        // 2、开关关闭时滑块坐标
        btnSlip_switch_off = new Rect(0,0,bkg_btnSlip.getWidth(),bkg_switchOn.getHeight());
    }
    
    //TODO 3、设置开关的状态
    public void setToggleState(boolean toggleStatus){
        isToggleStateOn = toggleStatus;
    }
    
    public interface OnToggleStatusListener {
        /**
         * 开关状态变化时,执行的方法
         * @param toggleState 变化后的开关状态
         */
        public abstract void onToggleStatus(boolean toggleState);
    }
    // TODO 4、监听滑动开关状态的变化,以便于来响应不同的事件
    public void setOnToggleStatusListener(OnToggleStatusListener listener) {
        toggleStatusListener = listener;
        isToggleStatusListenerOn = true;
    }
}