android自定义控件滑动开关详解

该文章为原创,转载请注明出处http://1.crazychen.sinaapp.com/?p=600

最近研究了一下android的自定义滑动开关,查找了网上的文章,都说得不是很详细,虽然思路大致相同,但是要通过动手实验一下,整理出自己的思路才懂。这篇文章希望能帮助其他朋友,实现这个功能。

首先,让我们来创建一个SlipButton类,让它集成View类型和OnTouchListener接口,这个SlipButton就是我们自定义的滑动开关类,实现它的三个构造方法(必须哦),还有重载onTouch()方法

public class SlipButton extends View implements OnTouchListener{
		
	public SlipButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);	
	}

	public SlipButton(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	public SlipButton(Context context) {
		super(context);
	}

	

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		return false;	
	}

	
}

我们需要三张图片,分别是开(男),关(女),还有滑动的圆块

s_btn_address_icon s_n_info_icon_men s_n_info_icon_women

为此,我获取这个三个资源对象

private Bitmap bg_on, bg_off, slip_btn;

创建一个init()方法来初始化

@SuppressLint("ClickableViewAccessibility")
	private void init(){
             bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_men);
	     bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_women);
	     slip_btn = BitmapFactory.decodeResource(getResources(), R.drawable.s_btn_address_icon);
	     setOnTouchListener(this);	    
	}

然后在每个构造方法里面,都调用init()。同时实现监听触摸事件

public class SlipButton extends View implements OnTouchListener{
	private Bitmap bg_on, bg_off, slip_btn;
	
	public SlipButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);	
		init();		
	}

	public SlipButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}
	
	public SlipButton(Context context) {
		super(context);
		init();
	}

	@SuppressLint("ClickableViewAccessibility")
	private void init(){
		 bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_men);
	     bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_women);
	     slip_btn = BitmapFactory.decodeResource(getResources(), R.drawable.s_btn_address_icon);
	     setOnTouchListener(this);	    
	}

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		return false;	
	}

	
}

接下面,我们先把开关画出来。创建activity_main.xml如下

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
  
  	
  	<com.example.androidtest.SlipButton
                        android:id="@+id/slipButton"
                        android:layout_width="80dip"
                        android:layout_height="30dip"
                        android:layout_marginTop="200dip"  
                         android:layout_marginLeft="200dip"         
                        />
</LinearLayout>

至于Activity部分,只要setContentView(R.layout.activity_main);就好了,相信大家都明白

然后在,我们复写View的onDraw()方法

@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        
      
        canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景                                           
        canvas.drawBitmap(slip_btn, 0, 0, paint);//画出按钮
	}

我们启动程序,看看效果如下

]VHURD(E73$39K}BDVK_EPT

接下来就是重头戏部分了哦,我们来理清楚逻辑。

定义两个属性

	private float downX, nowX=0;// 按下时的x,当前的x

 

首先是开关的状态由什么决定?

有两种情况,一种是滑块在拖动过程中,这时如果当前的x坐标(nowX)大于背景图片的1/2,就应该是开状态,如果小于1/2,就是关状态

另外一种就是触摸抬起的时,如果抬起时的nowX大于背景图片的1/2,就应该是开状态,如果小于1/2,就是关状态

综上,我们可以知道1/2就是区分点,改写onDraw

@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        
        if(nowX>bg_on.getWidth()/2)
        	canvas.drawBitmap(bg_on, matrix, paint);//画出关闭时的背景
        else
        	canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景
      
        canvas.drawBitmap(slip_btn, 0, 0, paint);//画出按钮
	}

然后我们在onTouch方法里面,来获得nowX

@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			nowX = event.getX();
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

这样我们就是实现了左右点击和滑动的背景切换了,大家可以看下效果。同时要说明,这里的ACTION_DOWN事件,其实没有实际作用。

背景切换了,下面我让滑块动起来,滑块的位置是由

canvas.drawBitmap(slip_btn, x, 0, paint);//画出按钮

其中的x决定的。为了却别滑动,和抬起,我创建一个标志

private boolean onSlip = false;

当onSlip=true时,说明是滑动状态。

我们分两种情况来讨论,先说抬起的情况。(抬起就是,你按按钮的另外一边,然后送手,状态就会切换,这个过程按钮没有滑动,而是从一边,直接到另外一边)

抬起要注意一个问题,就是你抬起时的nowX可能超出背景的宽度的1/2,这时,我们将nowX设置为bg_on.getWidth()-slip_btn.getWidth();就是背景长度减去按钮长度。也可能小于1/2,这时,我们将nowX设置为0

@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			onSlip = false;
			if(event.getX()>= bg_on.getWidth()/2){//超出1/2
                nowX = bg_on.getWidth() - slip_btn.getWidth(); 
			}else{
                nowX = 0;  
			}
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

然后稍微修改onDraw方法

@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        float x = slip_btn.getWidth();
                
        if(nowX>bg_on.getWidth()/2)
        	canvas.drawBitmap(bg_on, matrix, paint);//画出关闭时的背景
        else
        	canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景
      
       if(onSlip){
                          
       }else{
       	x = nowX;
       }
        
        canvas.drawBitmap(slip_btn, x, 0, paint);//画出按钮
}

OK,这里的抬起事件解决了,大家可以试试效果。

但是我们很快发现,拖动有问题,虽然拖出界以后会弹回来,所以我接下去将拖动的情况

在拖动时,我们要限制按钮的位置,不能越界,首先是右边的界限,判断依据是nowX不能超过bg_on.getWidth() - slip_btn.getWidth(),超过是,我们就将x设置为bg_on.getWidth() - slip_btn.getWidth()

其他情况应该是x = nowX - slip_btn.getWidth()/2;,但是这时x不能小于0,小于时,我设置x=0

先修改一下onTouch方法,将滑动时的onSlip设置为true

@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			onSlip = true;
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			onSlip = true;
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			onSlip = false;
			if(event.getX()>= bg_on.getWidth()/2){//超出1/2
                nowX = bg_on.getWidth() - slip_btn.getWidth(); 
			}else{
                nowX = 0;  
			}
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

 

在修改ondraw方法

@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        float x = slip_btn.getWidth();
                
        if(nowX>bg_on.getWidth()/2)
        	canvas.drawBitmap(bg_on, matrix, paint);//画出关闭时的背景
        else
        	canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景
      
       if(onSlip){
    	   if(nowX>bg_on.getWidth() - slip_btn.getWidth()){
          	 x = bg_on.getWidth()-slip_btn.getWidth();
           } else{
          	 x = nowX - slip_btn.getWidth()/2;
          	 if(x<0) x=0;
           }      
       }else{
    	   x = nowX;
       }
        
        canvas.drawBitmap(slip_btn, x, 0, paint);//画出按钮
	}

OK,到这里为止,我们成功实现了滑动按钮,应该说思路还是简单清晰的。是不是SOeasy!

再次贴出完整代码

public class SlipButton extends View implements OnTouchListener{
	private Bitmap bg_on, bg_off, slip_btn;
	private float downX, nowX=0;// 按下时的x,当前的x
	private boolean onSlip = false;
	
	public SlipButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);	
		init();		
	}

	public SlipButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}
	
	public SlipButton(Context context) {
		super(context);
		init();
	}

	@SuppressLint("ClickableViewAccessibility")
	private void init(){
		 bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_men);
	     bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_women);
	     slip_btn = BitmapFactory.decodeResource(getResources(), R.drawable.s_btn_address_icon);
	     setOnTouchListener(this);	    
	}

	@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        float x = slip_btn.getWidth();
                
        if(nowX>bg_on.getWidth()/2)
        	canvas.drawBitmap(bg_on, matrix, paint);//画出关闭时的背景
        else
        	canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景
      
       if(onSlip){
    	   if(nowX>bg_on.getWidth() - slip_btn.getWidth()){
          	 x = bg_on.getWidth()-slip_btn.getWidth();
           } else{
          	 x = nowX - slip_btn.getWidth()/2;
          	 if(x<0) x=0;
           }      
       }else{
    	   x = nowX;
       }
        
        canvas.drawBitmap(slip_btn, x, 0, paint);//画出按钮
	}	
	
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			onSlip = true;
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			onSlip = true;
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			onSlip = false;
			if(event.getX()>= bg_on.getWidth()/2){//超出1/2
                nowX = bg_on.getWidth() - slip_btn.getWidth(); 
			}else{
                nowX = 0;  
			}
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

	
}

 

下面我在为滑动按钮添加回调事件,便于Activity的监听

public interface OnChangedListener{
		 public void OnChanged(SlipButton slipButton, boolean checkState);  
	}

增加一个给外界的接口

然后Activity继承这个接口,实现接口里面的方法

package com.example.androidtest;

import java.io.File;

import org.apache.http.Header;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.example.androidtest.SlipButton.OnChangedListener;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;


public class MainActivity extends Activity implements OnChangedListener{	 	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 

    }
	 	 
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			GGView.enable=false;
			System.exit(0);			
		}
		return true;
	}

	@Override
	public void OnChanged(SlipButton slipButton, boolean checkState) {
		// TODO Auto-generated method stub
		
	}
}

在Activity里面,我们再获得滑动按钮对象,

 SlipButton slipButton = (SlipButton)findViewById(R.id.slipButton);

同时,我们在SlipButton里面,创建一个listener,还有一个表示按钮状态的boolean

private OnChangedListener listener;
	private boolean nowStatus = false;

初始化listner,我增加一个方法

public void setOnChangedListener(OnChangedListener listener){  
        this.listener = listener;  
    }

然后在Activity里面,就看把Activity传进去,当listner了

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SlipButton slipButton = (SlipButton)findViewById(R.id.slipButton);  
        slipButton.setOnChangedListener(this);
    }

当然,每次状态改变的时候,我们要通知listner,在SlipeButton里面

@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			onSlip = true;
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			onSlip = true;
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			onSlip = false;
			if(event.getX()>= bg_on.getWidth()/2){//超出1/2
                             nowStatus = true;
                             nowX = bg_on.getWidth() - slip_btn.getWidth(); 
			}else{
                             nowStatus = false;
                             nowX = 0;  
			}
			if(listener!=null)
				listener.OnChanged(SlipButton.this, nowStatus);
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

 

把当前状态传给listener,这样Activity就可以获得当前状态了,然后在Activity里面覆写接口方法就可以了

public class MainActivity extends Activity implements OnChangedListener{	 	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SlipButton slipButton = (SlipButton)findViewById(R.id.slipButton);  
        slipButton.setOnChangedListener(this);
    }
	 	 
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			GGView.enable=false;
			System.exit(0);			
		}
		return true;
	}

	@Override
	public void OnChanged(SlipButton slipButton, boolean checkState) {
		if(checkState){
            System.out.println("男");
        }else{
            System.out.println("女");
        }		
	}
}

完成监听了哦,这里的回调思想,大家好好体会。

下面贴出完整代码

package com.example.androidtest;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class SlipButton extends View implements OnTouchListener{
	private Bitmap bg_on, bg_off, slip_btn;
	private float downX, nowX=0;// 按下时的x,当前的x
	private boolean onSlip = false;
	private OnChangedListener listener;
	private boolean nowStatus = false;
	
	public SlipButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);	
		init();		
	}

	public SlipButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}
	
	public SlipButton(Context context) {
		super(context);
		init();
	}

	@SuppressLint("ClickableViewAccessibility")
	private void init(){
		 bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_men);
	     bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.s_n_info_icon_women);
	     slip_btn = BitmapFactory.decodeResource(getResources(), R.drawable.s_btn_address_icon);
	     setOnTouchListener(this);	    
	}

	@SuppressLint("DrawAllocation")
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
        Matrix matrix = new Matrix();  
        Paint paint = new Paint();
        float x = slip_btn.getWidth();
                
        if(nowX>bg_on.getWidth()/2)
        	canvas.drawBitmap(bg_on, matrix, paint);//画出关闭时的背景
        else
        	canvas.drawBitmap(bg_off, matrix, paint);//画出关闭时的背景
      
       if(onSlip){
    	   if(nowX>bg_on.getWidth() - slip_btn.getWidth()){
          	 x = bg_on.getWidth()-slip_btn.getWidth();
           } else{
          	 x = nowX - slip_btn.getWidth()/2;
          	 if(x<0) x=0;
           }      
       }else{
    	   x = nowX;
       }
        
        canvas.drawBitmap(slip_btn, x, 0, paint);//画出按钮
	}	
	
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction()){
		case MotionEvent.ACTION_MOVE://滑动时
			onSlip = true;
			nowX = event.getX();
			break;	
		case MotionEvent.ACTION_DOWN://按下
			onSlip = true;
			downX = event.getX();
			nowX = downX;
			break;
		case MotionEvent.ACTION_UP://触摸抬起
			onSlip = false;
			if(event.getX()>= bg_on.getWidth()/2){//超出1/2
				nowStatus = true;
                nowX = bg_on.getWidth() - slip_btn.getWidth(); 
			}else{
				nowStatus = false;
                nowX = 0;  
			}
			if(listener!=null)
				listener.OnChanged(SlipButton.this, nowStatus);
			break;					
		default:
			return false;
		}
		invalidate();
		return true;	
	}

	public void setOnChangedListener(OnChangedListener listener){  
        this.listener = listener;  
    }  
	
	public interface OnChangedListener{
		 public void OnChanged(SlipButton slipButton, boolean checkState);  
	}
}

 

package com.example.androidtest;

import java.io.File;

import org.apache.http.Header;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.example.androidtest.SlipButton.OnChangedListener;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;


public class MainActivity extends Activity implements OnChangedListener{	 	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SlipButton slipButton = (SlipButton)findViewById(R.id.slipButton);  
        slipButton.setOnChangedListener(this);
    }
	 	 
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			GGView.enable=false;
			System.exit(0);			
		}
		return true;
	}

	@Override
	public void OnChanged(SlipButton slipButton, boolean checkState) {
		if(checkState){
			System.out.println("男");
		}else{
			System.out.println("女");
		}
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值