这次为大家介绍一个自己模仿的IOS中滑动按钮的功能,IOS里的圆角滑动按钮是一个很不错的控件,体验比较好,那么Android上能不能实现相似的控件呢?答案肯定是能,实际上不但这样的效果能够实现,只要大家发挥想象力就能够实现更多更好的控件。废话不多说,开始为大家讲解控件的实现过程。
先讲一下大体的代码流程,实现一个滑动按钮在大方向上与上篇博客的GIF图片播放是相似的,都是实现自定义控件,用的都是自定义控件中继承已有控件进行扩展和修改的方式。既然是一个滑动按钮那么必然存在开和关两种状态,另外,还需要一个控制开关的组成部分,由此可以了解,这个控件需要开关两张图片和一个开关控制图片。我们需要复写View里的OnDraw()方法和onTouch()方法(用于获取必要参数)。下面是本次代码实例的工程结构:
控件中用到的三个图片(抠图的时候没仔细抠,大家包涵):
bg_on bg_offslipper_btn
如上,SlipButton就是我们将要实现的滑动按钮代码。工程结构比较简单接下来,上代码,在代码中详细介绍实现过程
package com.example.cirbutton;
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;
/**
* 仿IOS滑动按钮
* @author yufc
*
*/
public class SlipButton extends View implements OnTouchListener{
/**
* 滑动按钮
*/
private Bitmap bg_on, bg_off, slipper_btn;
/**
* 记录点击位置和按钮位置
*/
private float downX, nowX;
/**
* 滑动标志
*/
private boolean onSliping = false;
/**
* 记录当前按钮状态开还是关
*/
private boolean nowStatus = false;
/**
* 滑动监听器,根据滑动
*/
private OnSlipChangedListener listener;
/**
* 复写构造方
* @param context
*/
public SlipButton(Context context) {
super(context);
init();
}
/**
* 复写两个参数的构造方法,以后如果需要扩展时可以在这里读入一些xml文件里的参数
* @param context
* @param attrs
*/
public SlipButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 初始化操作把图片资源载入进来,
* 为控件设置监听器
*/
public void init(){
//图片资源
bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.on_btn);
bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.off_btn);
slipper_btn = BitmapFactory.decodeResource(getResources(), R.drawable.white_btn);
//设置监听器
setOnTouchListener(this);
}
//复写的OnDraw方法
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//矩阵参数,必要,不需知晓具体作用
Matrix matrix = new Matrix();
//画笔参数,必要,不需知晓具体作用
Paint paint = new Paint();
//开关按钮的位置
float x = 0;
//按钮的控制逻辑档滑动不超过一半时显示开,超过则显示关
if (nowX < (bg_on.getWidth()/2)){
//绘制“关”按钮
canvas.drawBitmap(bg_off, matrix, paint);
}else{
//绘制“开”按钮
canvas.drawBitmap(bg_on, matrix, paint);
}
if (onSliping) {
//控制右边界
if(nowX >= bg_on.getWidth())
//
x = bg_on.getWidth() - slipper_btn.getWidth()/2;
else
x = nowX - slipper_btn.getWidth()/2;
}else {
if(nowStatus){
//把开关按钮的位置设置到右边
x = bg_on.getWidth() - slipper_btn.getWidth();
}else{
//把开关按钮的位置设置到左边
x = 0;
}
}
//根据x结果来绘制
if (x < 0 ){
x = 0;
}
else if(x > bg_on.getWidth() - slipper_btn.getWidth()){
x = bg_on.getWidth() - slipper_btn.getWidth();
}
//绘制开关控制按钮
canvas.drawBitmap(slipper_btn, x , 0, paint);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
if (event.getX() > bg_off.getWidth() || event.getY() > bg_off.getHeight()){
return false;
}else{
onSliping = true;
downX = event.getX();
nowX = downX;
}
break;
}
case MotionEvent.ACTION_MOVE:{
nowX = event.getX();
break;
}
case MotionEvent.ACTION_UP:{
onSliping = false;
if(event.getX() >= (bg_on.getWidth()/2)){
nowStatus = true;
nowX = bg_on.getWidth() - slipper_btn.getWidth();
}else{
nowStatus = false;
nowX = 0;
}
if(listener != null){
listener.OnChanged(SlipButton.this, nowStatus);
}
break;
}
}
//重新绘制控件
invalidate();
return true;
}
/**
* 为滑动按钮添加状态监听器
* @param listener
*/
public void setSlipOnChangedListener(OnSlipChangedListener listener){
this.listener = listener;
}
/**
* 设置按钮状态的方法
* @param checked
*/
public void setChecked(boolean checked){
if(checked){
nowX = bg_off.getWidth();
}else{
nowX = 0;
}
nowStatus = checked;
}
/**
* 回调接口
* @author yufc
*
*/
public interface OnSlipChangedListener {
public void OnChanged(SlipButton wiperSwitch, boolean checkState);
}
/**
* 得到现在的开关状态״̬
* @return
*/
public boolean getSwitchNowStatus(){
return nowStatus;
}
}
如代码中所示,定义了downX,nowX两个变量来记录点击下的位置和按钮的当前位置,一个onSliping变量记录滑动状态,nowStatus记录按钮开或者关的状态。然后复写构造方法,这里复写了两个,原生控件的代码里会复写三个构造方法(参见Android源代码),不过第三个参数不常用,所以这里只写了两个。
然后写一个初始化方法用来初始化变量,载入图片资源并为滑动按钮设置监听器。
下面是主要代码片段,片段后跟代码详解
if (nowX < (bg_on.getWidth()/2)){
//绘制“关”按钮
canvas.drawBitmap(bg_off, matrix, paint);
}else{
//绘制“开”按钮
canvas.drawBitmap(bg_on, matrix, paint);
}
控制如果nowX(即当前手指所在的位置)大于“开”图片的一半时就把背景改为“关”图片,如果当前背景图片小于“开”图片的一半则设置为开图片
if (onSliping) {
//控制边界
if(nowX >= bg_on.getWidth())
//
x = bg_on.getWidth() - slipper_btn.getWidth();
else
x = nowX - slipper_btn.getWidth()/2;
}else {
if(nowStatus){
//把开关按钮的位置设置到右边
x = bg_on.getWidth() - slipper_btn.getWidth();
}else{
//把开关按钮的位置设置到左边
x = 0;
}
}
onSliping是滑动状态标识,如果是正在滑动的状态就先判断边界如果超出边界则使slipper_btn置回边界以内。否则slipper_btn的位置为当前手指位置减掉slipper_btn宽度的一半,如果没有滑动那slipper_btn就是在左边或者右边。
case MotionEvent.ACTION_DOWN:{
if (event.getX() > bg_off.getWidth() || event.getY() > bg_off.getHeight()){
return false;
}else{
onSliping = true;
downX = event.getX();
nowX = downX;
}
这是onTouch方法里的方法用于设置滑动状态标识和记录点击位置
case MotionEvent.ACTION_MOVE:{
nowX = event.getX();
手指移动时记录下现在的downX
case MotionEvent.ACTION_UP:{
onSliping = false;
if(event.getX() >= (bg_on.getWidth()/2)){
nowStatus = true;
nowX = bg_on.getWidth() - slipper_btn.getWidth();
}else{
nowStatus = false;
nowX = 0;
}
if(listener != null){
listener.OnChanged(SlipButton.this, nowStatus);
}
当手指抬起时,当前slipper_btn位置的设置和开关状态的设置,如果设置了监听器需要调用监听器的方法,最后invalidate()一下(也就是在手指抬起时重新绘制View)
最后是定义回调接口和监听器设置方法,大功告成。
看一下如果使用我们这个自定义的滑动按钮,首先是要在布局文件声明出来
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rootLinearLayout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/textView"
android:text="测试滑动按钮"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.example.cirbutton.SlipButton
android:id="@+id/wiperSwitch1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView" />
</RelativeLayout>
然后是主Activity里使用
package com.example.cirbutton;
import com.example.cirbutton.SlipButton.OnSlipChangedListener;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SlipButton slipButton = (SlipButton) findViewById(R.id.wiperSwitch1);
//当通过滑动来改变开关状态时
slipButton.setSlipOnChangedListener(new OnSlipChangedListener() {
@Override
public void OnChanged(SlipButton wiperSwitch, boolean checkState) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(), "按钮状态改变", Toast.LENGTH_SHORT).show();
}
});
//当通过点击来改变开关状态时
slipButton.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
// TODO Auto-generated method stub
if(slipButton.getSwitchNowStatus()==false){
slipButton.setChecked(true);
}else{
slipButton.setChecked(false);
}
}
});
}
}
贴出本次的Manifest代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cirbutton"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.cirbutton.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
少不了的效果图: