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

 大凡在公司做客户端产品开发的都会发现,AndroidiOS的差异化,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结构图:

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

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.example.slidebutton.view;  
  2.   
  3. import com.example.slidebutton.R;  
  4. import com.example.slidebutton.view.interf.OnToggleStateChangeListener;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.BitmapFactory;  
  9. import android.graphics.Canvas;  
  10. import android.util.AttributeSet;  
  11. import android.view.MotionEvent;  
  12. import android.view.View;  
  13.   
  14. public class SlideButton extends View {  
  15.   
  16.     /** 滑动开关的背景 */  
  17.     private Bitmap slideButtonBG;  
  18.     /** 滑动块的背景 */  
  19.     private Bitmap switchBG;  
  20.     /** 设置开关的状态,打开/关闭。 默认:关闭 */  
  21.     private boolean currentState = false;  
  22.     /** 当前滑动块的移动距离 */  
  23.     private int currentX;  
  24.     /** 记录当前滑动块滑动的状态。默认,false */  
  25.     private boolean isSliding = false;  
  26.     /** 开关状态改变监听 */  
  27.     private OnToggleStateChangeListener mListener;  
  28.   
  29.     public SlideButton(Context context, AttributeSet attrs) {  
  30.         super(context, attrs);  
  31.   
  32.         initBitmap();  
  33.     }  
  34.   
  35.     private void initBitmap() {  
  36.         slideButtonBG = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);  
  37.         switchBG = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);  
  38.     }  
  39.   
  40.     /** 
  41.      * 移动效果的处理 
  42.      */  
  43.     @Override  
  44.     public boolean onTouchEvent(MotionEvent event) {  
  45.         switch (event.getAction()) {  
  46.         case MotionEvent.ACTION_DOWN: // 手指按下  
  47.             currentX = (int) event.getX();  
  48.             isSliding = true;  
  49.             break;  
  50.         case MotionEvent.ACTION_MOVE: // 手指移动  
  51.             currentX = (int) event.getX();  
  52.             break;  
  53.         case MotionEvent.ACTION_UP: // 手指抬起  
  54.             isSliding = false;  
  55.             // 判断当前滑动块,偏向于哪一边,如果滑动块的中心点<背景的中心点,设置为关闭状态  
  56.             int bgCenter = switchBG.getWidth() / 2;  
  57.             boolean state = currentX >= bgCenter; // 改变后的状态  
  58.             // 手指抬起时,回调监听,返回当前的开关状态  
  59.             if (state != currentState && mListener != null) {  
  60.                 mListener.onToggleStateChange(state);  
  61.             }  
  62.             currentState = state;  
  63.             break;  
  64.         default:  
  65.             break;  
  66.         }  
  67.         invalidate(); // 刷新控件,该方法会调用onDraw(Canvas canvas)方法  
  68.         return true// 自己处理事件,不让父类负责消耗事件  
  69.     }  
  70.   
  71.     /** 
  72.      * 测量当前控件宽高时回调 
  73.      */  
  74.     @Override  
  75.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  76.         // TODO Auto-generated method stub  
  77.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  78.         // 设置开关的宽和高  
  79.         setMeasuredDimension(switchBG.getWidth(), switchBG.getHeight());  
  80.     }  
  81.   
  82.     /** 
  83.      * 绘制控件 
  84.      */  
  85.     @Override  
  86.     protected void onDraw(Canvas canvas) {  
  87.         // TODO Auto-generated method stub  
  88.         super.onDraw(canvas);  
  89.         // 1,滑动开关背景绘制到控件  
  90.         canvas.drawBitmap(switchBG, 00null);  
  91.         // 2,绘制滑动块显示的位置,开启或关闭  
  92.         if (isSliding) {  
  93.             int left = currentX - slideButtonBG.getWidth() / 2// 处理手指触点,将触点从slidingButton的左边移动到中间  
  94.             if (left < 0) {  
  95.                 left = 0;  
  96.             } else if (left > switchBG.getWidth() - slideButtonBG.getWidth()) {  
  97.                 left = switchBG.getWidth() - slideButtonBG.getWidth();  
  98.             }  
  99.             canvas.drawBitmap(slideButtonBG, left, 0null);  
  100.         } else {  
  101.             if (currentState) {  
  102.                 // 绘制打开状态  
  103.                 canvas.drawBitmap(slideButtonBG, switchBG.getWidth() - slideButtonBG.getWidth(), 0null);  
  104.             } else {  
  105.                 // 绘制关闭状态  
  106.                 canvas.drawBitmap(slideButtonBG, 00null);  
  107.             }  
  108.         }  
  109.     }  
  110.   
  111.     public void setToggleState(boolean b) {  
  112.         currentState = b;  
  113.     }  
  114.   
  115.     /** 
  116.      * 对外设置监听方法 
  117.      *  
  118.      * @param listener 
  119.      */  
  120.     public void setOnToggleStateChangeListener(OnToggleStateChangeListener listener) {  
  121.         this.mListener = listener;  
  122.     }  
  123.   
  124. }  
    “开关按钮”的是继承自View来实现的,关于View的构造函数:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public SlideButton(Context context) {  
  2.         super(context);  
  3.         // TODO Auto-generated constructor stub  
  4.     }  
该构造用于该组件仅仅在代码中实例化,不能在布局文件中声明

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public SlideButton(Context context, AttributeSet attrs) {  
  2.         super(context, attrs);  
  3.         // TODO Auto-generated constructor stub  
  4.     }  

声明该构造后,可以在布局文件中声明出这个控件,配置一些相关属性的。一般自定义组件都复写这个构造方法。

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

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public SlideButton(Context context, AttributeSet attrs, int defStyle) {  
  2.         super(context, attrs, defStyle);  
  3.         // TODO Auto-generated constructor stub  
  4.     }  
这是View的第三个构造,可以在布局文件中声明之外,还需要制定组件的显示样式,用的较少。

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

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

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

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.example.slidebutton.view.interf;  
  2.   
  3. /** 
  4.  * 开关状态改变监听事件 
  5.  *  
  6.  * @author Administrator 
  7.  *  
  8.  */  
  9. public interface OnToggleStateChangeListener {  
  10.     /** 
  11.      * 当开关状态改变回调此方法 
  12.      *  
  13.      * @param b 
  14.      *            当前开关的最新状态 
  15.      */  
  16.     void onToggleStateChange(boolean b);  
  17. }  

关于监听的内部实现,请参考SlidingButton自定义组件代码。这里主要为外部引用该控件的界面提供了一个设置监听的方法setOnToggleStateChangeListener(OnToggleStateChangeListener listener),在判断每次手指抬起的时候,说明开关状态改变的过程结束,需要在那里为监听器设置回调参数,即当前的开关的状态boolean变量。这样,外部引用该控件的界面就可以拿到这个boolean值,做出相应的处理。那么,以下就是外部引用该自定义组件的主要代码:

布局文件,activity_main.xml:

[html]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <com.example.slidebutton.view.SlideButton  
  7.         android:id="@+id/slidebutton"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:layout_centerInParent="true" />  
  11.   
  12. </RelativeLayout>  
主界面的代码MainActivity.java:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.example.slidebutton;  
  2.   
  3. import com.example.slidebutton.view.SlideButton;  
  4. import com.example.slidebutton.view.interf.OnToggleStateChangeListener;  
  5.   
  6. import android.os.Bundle;  
  7. import android.widget.Toast;  
  8. import android.app.Activity;  
  9.   
  10. public class MainActivity extends Activity implements OnToggleStateChangeListener {  
  11.   
  12.     public SlideButton slideButton;  
  13.   
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.activity_main);  
  18.   
  19.         slideButton = (SlideButton) findViewById(R.id.slidebutton);  
  20.         slideButton.setToggleState(true);  
  21.         slideButton.setOnToggleStateChangeListener(this);  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onToggleStateChange(boolean b) {  
  26.         // TODO Auto-generated method stub  
  27.         if (b) {  
  28.             Toast.makeText(MainActivity.this"开关打开", Toast.LENGTH_SHORT).show();  
  29.         } else {  
  30.             Toast.makeText(MainActivity.this"开关关闭", Toast.LENGTH_SHORT).show();  
  31.         }  
  32.     }  
  33.   
  34. }  

以下是运行效果图:

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


源码请在这里下载

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义控件Android开发中常见的任务之一。下面是一步一步教你如何自定义控件的简要指南: 第一步:创建一个新的Java类作为你的自定义控件。 首先,创建一个新的Java类,可以命名为你想要的控件名称。这个类应该继承自Android框架中的现有控件,例如View、TextView等。例如,如果你想要创建一个自定义按钮,可以创建一个名为CustomButton的类,并让它继承自Button类。 第二步:实现构造函数和属性。 在你的自定义控件类中,你可以实现构造函数和属性,以便对控件进行初始化和设置。你可以定义自己的属性,例如颜色、大小等,以及相应的getter和setter方法。 第三步:重写绘制方法。 要自定义控件的外观,你需要重写它的绘制方法。最常用的方法是重写`onDraw()`方法,在其中使用Canvas绘制你想要的形状、文本等。 第四步:处理用户交互。 如果你的自定义控件需要与用户进行交互,你可以重写相应的触摸事件(例如`onTouchEvent()`)或点击事件(例如`setOnClickListener()`)来处理用户操作。 第五步:在布局文件中使用自定义控件。 完成以上步骤后,你可以在布局文件中使用你的自定义控件了。只需在布局文件中添加一个与你的控件类名相对应的XML标签,并设置相应的属性。 这只是一个简要的指南,帮助你开始自定义控件的过程。在实际开发中,你可能需要更多的步骤和细节来完成你的自定义控件。你可以参考Android官方文档或其他教程来获取更多信息和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值