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

Android自定义控件——仿ios开关按钮

2015-02-08 23:19 507 查看
大凡在公司做客户端产品开发的都会发现,android和ios的差异化,ios得益于“老乔”的精心设计,界面用户体验做到了极致,而android秉承开源思想,界面用户体验百家各有其长,相互不得统一。不说废话,先上图,看看ios的“开关按钮”:




往往在公司,产品设计原型优先参考了ios的设计,这下可苦了android开发者,android开发者被迫要写和ios几乎同样的效果,这种情况应该很常见吧!例如,下面我提到的“开关按钮”,在ios中是集成好的,拿来就用,但在android里面确实在android 4.0以后才出现ToggleButton的组件,抛开界面不谈(实际不是很美观),那么看兼容性吧,ToggleButton是从4.0以后才开始有的东西,那么2.3的系统我们不能就此忽略了吧,2.3在市场占有上还是有很大比重的。因此,能做到和ios同样效果的“开关按钮”,我们只能“苦逼的”用java代码去绘制出这样一个组件了。

简单讲讲自绘组件的方法,Android下自绘组件大致分为继承View和继承ViewGroup两种。

实现步骤和对应的方法:

测量宽高 --> 排版(布置控件的位置) --> 绘制到屏幕

| | |

onMeasure onLayout onDraw

值得注意的是:自定义组件若是继承ViewGroup来实现,就必须实现上述3个步骤,尤其是排版,这里需要制定组件的显示位置。自定义组件是继承View来实现的话,排版这步不需要了,只完成测量和绘制即可。

以下我的项目Demo结构图:



自定义“开关按钮”主要实现代码:

package com.example.slidebutton.view;  
  
import com.example.slidebutton.R;  
import com.example.slidebutton.view.interf.OnToggleStateChangeListener;  
  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.graphics.Canvas;  
import android.util.AttributeSet;  
import android.view.MotionEvent;  
import android.view.View;  
  
public class SlideButton extends View {  
  
    /** 滑动开关的背景 */  
    private Bitmap slideButtonBG;  
    /** 滑动块的背景 */  
    private Bitmap switchBG;  
    /** 设置开关的状态,打开/关闭。 默认:关闭 */  
    private boolean currentState = false;  
    /** 当前滑动块的移动距离 */  
    private int currentX;  
    /** 记录当前滑动块滑动的状态。默认,false */  
    private boolean isSliding = false;  
    /** 开关状态改变监听 */  
    private OnToggleStateChangeListener mListener;  
  
    public SlideButton(Context context, AttributeSet attrs) {  
        super(context, attrs);  
  
        initBitmap();  
    }  
  
    private void initBitmap() {  
        slideButtonBG = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);  
        switchBG = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);  
    }  
  
    /** 
     * 移动效果的处理 
     */  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        switch (event.getAction()) {  
        case MotionEvent.ACTION_DOWN: // 手指按下  
            currentX = (int) event.getX();  
            isSliding = true;  
            break;  
        case MotionEvent.ACTION_MOVE: // 手指移动  
            currentX = (int) event.getX();  
            break;  
        case MotionEvent.ACTION_UP: // 手指抬起  
            isSliding = false;  
            // 判断当前滑动块,偏向于哪一边,如果滑动块的中心点<背景的中心点,设置为关闭状态  
            int bgCenter = switchBG.getWidth() / 2;  
            boolean state = currentX >= bgCenter; // 改变后的状态  
            // 手指抬起时,回调监听,返回当前的开关状态  
            if (state != currentState && mListener != null) {  
                mListener.onToggleStateChange(state);  
            }  
            currentState = state;  
            break;  
        default:  
            break;  
        }  
        invalidate(); // 刷新控件,该方法会调用onDraw(Canvas canvas)方法  
        return true; // 自己处理事件,不让父类负责消耗事件  
    }  
  
    /** 
     * 测量当前控件宽高时回调 
     */  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // TODO Auto-generated method stub  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        // 设置开关的宽和高  
        setMeasuredDimension(switchBG.getWidth(), switchBG.getHeight());  
    }  
  
    /** 
     * 绘制控件 
     */  
    @Override  
    protected void onDraw(Canvas canvas) {  
        // TODO Auto-generated method stub  
        super.onDraw(canvas);  
        // 1,滑动开关背景绘制到控件  
        canvas.drawBitmap(switchBG, 0, 0, null);  
        // 2,绘制滑动块显示的位置,开启或关闭  
        if (isSliding) {  
            int left = currentX - slideButtonBG.getWidth() / 2; // 处理手指触点,将触点从slidingButton的左边移动到中间  
            if (left < 0) {  
                left = 0;  
            } else if (left > switchBG.getWidth() - slideButtonBG.getWidth()) {  
                left = switchBG.getWidth() - slideButtonBG.getWidth();  
            }  
            canvas.drawBitmap(slideButtonBG, left, 0, null);  
        } else {  
            if (currentState) {  
                // 绘制打开状态  
                canvas.drawBitmap(slideButtonBG, switchBG.getWidth() - slideButtonBG.getWidth(), 0, null);  
            } else {  
                // 绘制关闭状态  
                canvas.drawBitmap(slideButtonBG, 0, 0, null);  
            }  
        }  
    }  
  
    public void setToggleState(boolean b) {  
        currentState = b;  
    }  
  
    /** 
     * 对外设置监听方法 
     *  
     * @param listener 
     */  
    public void setOnToggleStateChangeListener(OnToggleStateChangeListener listener) {  
        this.mListener = listener;  
    }  
  
}


“开关按钮”的是继承自View来实现的,关于View的构造函数:

public SlideButton(Context context) {  
        super(context);  
        // TODO Auto-generated constructor stub  
    }
该构造用于该组件仅仅在代码中实例化,不能在布局文件中声明

public SlideButton(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        // TODO Auto-generated constructor stub  
    }
声明该构造后,可以在布局文件中声明出这个控件,配置一些相关属性的。一般自定义组件都复写这个构造方法。

关于android自定义属性,请参考Android自定义控件——自定义属性

public SlideButton(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
        // TODO Auto-generated constructor stub  
    }
这是View的第三个构造,可以在布局文件中声明之外,还需要制定组件的显示样式,用的较少。

复写父类View的构造器之后,就需要复写View的另两个重要的方法了,onMeasure和onDraw。onMeasure是用于测量组件的大小宽高的方法,在这里,只处理“开关按钮”的背景图的宽高,使用setMeasuredDimension方法,指定测量后的值。然后在onDraw方法中,在画布Canvas上画出这个自定义组件,里面会包含一系列的逻辑判断,请大家仔细阅读源码,这里不介绍了。

为了增强“开关按钮”的用户体验,需要判断按钮的滑动方向来确定按钮的状态,所以需要复写onTouchEvent方法,在这个方法里处理手指触发的各种状态。值得一提的是,在处理完手指移动的触发后,接下来就是重绘组件,达到动态的效果,这时必须在每次触发完成时,调用一下View的invalidate()方法,该方法的作用是:刷新控件,该方法会调用onDraw(Canvas canvas)方法。

最后,“开发按钮”要与使用该控件的界面或者用户实现交互或状态传递,这里需要写一个Java回调函数,用于在主界面上回调监听“开关按钮”的状态,代码如下:

package com.example.slidebutton.view.interf;  
/** 
 * 开关状态改变监听事件 
 *  
 * @author Administrator 
 *  
 */  
public interface OnToggleStateChangeListener {  
    /** 
     * 当开关状态改变回调此方法 
     *  
     * @param b 
     *            当前开关的最新状态 
     */  
    void onToggleStateChange(boolean b);  
}
关于监听的内部实现,请参考SlidingButton自定义组件代码。这里主要为外部引用该控件的界面提供了一个设置监听的方法setOnToggleStateChangeListener(OnToggleStateChangeListener listener),在判断每次手指抬起的时候,说明开关状态改变的过程结束,需要在那里为监听器设置回调参数,即当前的开关的状态boolean变量。这样,外部引用该控件的界面就可以拿到这个boolean值,做出相应的处理。那么,以下就是外部引用该自定义组件的主要代码:

布局文件,activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <com.example.slidebutton.view.SlideButton  
        android:id="@+id/slidebutton"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_centerInParent="true" />  
  
</RelativeLayout>  
主界面的代码MainActivity.java:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
package com.example.slidebutton;  
  
import com.example.slidebutton.view.SlideButton;  
import com.example.slidebutton.view.interf.OnToggleStateChangeListener;  
  
import android.os.Bundle;  
import android.widget.Toast;  
import android.app.Activity;  
  
public class MainActivity extends Activity implements OnToggleStateChangeListener {  
  
    public SlideButton slideButton;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        slideButton = (SlideButton) findViewById(R.id.slidebutton);  
        slideButton.setToggleState(true);  
        slideButton.setOnToggleStateChangeListener(this);  
    }  
  
    @Override  
    public void onToggleStateChange(boolean b) {  
        // TODO Auto-generated method stub  
        if (b) {  
            Toast.makeText(MainActivity.this, "开关打开", Toast.LENGTH_SHORT).show();  
        } else {  
            Toast.makeText(MainActivity.this, "开关关闭", Toast.LENGTH_SHORT).show();  
        }  
    }  
  
}


以下是运行效果图:



以上内容,有诸多不专业不正确或者疏忽的地方,欢迎大家批评指正,共同学习~

转载地址:http://blog.csdn.net/allen315410/article/details/39319057

源码下载地址:http://download.csdn.net/detail/itjavawfc/8433877
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: