该文章为原创,转载请注明出处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("女"); } } }