您的位置:首页 > 移动开发 > Android开发

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

2014-12-16 12:33 246 查看
该文章为原创,转载请注明出处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("女");
}
}
}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: