该文章为原创,转载请注明出处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;
}
}
我们需要三张图片,分别是开(男),关(女),还有滑动的圆块
为此,我获取这个三个资源对象
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);//画出按钮
}
我们启动程序,看看效果如下
接下来就是重头戏部分了哦,我们来理清楚逻辑。
定义两个属性
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("女");
}
}
}