Android自定义View实现开关按钮

前言:Android自定义View对于刚入门乃至工作几年的程序员来说都是非常恐惧的,但也是Android进阶学习的必经之路,平时项目中经常会有一些苛刻的需求,我们可以在GitHub上找到各种各样的效果,能用则用,不能用自己花功夫改改也能草草了事。不过随着工作经验和工作性质,越来越觉得自定义View是时候有必要自己花点功夫研究一下。

一、经过这两天的努力,自己也尝试着写了一个Demo,效果很简单,就是开关按钮的实现。

可能有的人会说这效果so easy,找UI切三张图就完事了,何必大费周折自定义。你说的没错,不过这里只是用来学习自定义View来展示这么一个常见案例。

自定义控件

1.为什么自定义View?

Android自身带的控件不能满足需求, 需要根据自己的需求定义控件.

2.Android 的界面绘制流程?

onMeasure()——onLayout()——onDraw()方法都在Activity生命周期的onResume()方法之后执行。

3.Android自定义View的方式?

集成View:View流程

onMeasure() (在这个方法里指定自己的宽高) -> onDraw() (绘制自己的内容)

集成ViewGroup:ViewGroup流程

onMeasure() (指定自己的宽高, 所有子View的宽高)-> onLayout() (摆放所有子View) -> onDraw() (绘制内容)

自定义View实现开关按钮步骤:

写个类继承View,

拷贝包含包名的全路径到xml中,

界面中找到该控件, 设置初始信息,

根据需求绘制界面内容,

响应用户的触摸事件,

创建一个状态更新监听.

1.自定义ToggleView集成View,并且重新三个构造方法。

注意:构造方法为什么要重写三个?

ToggleView(Context context)一个参数的构造方法是用于代码创建控件时调用的

ToggleView(Context context, AttributeSet attrs)用于在xml里使用, 可指定自定义属性

ToggleView(Context context, AttributeSet attrs, int defStyle)用于在xml里使用, 可指定自定义属性, 如果指定了样式, 则走此构造函数

我们在XML中定义了背景图片、开关按钮图片和开关默认状态,要获取在XML文件定义的属性就在包含三个参数的构造方法里用TypedArray类来获取。

在attrs.xml声明节点declare-styleable

<declare-styleable name="ToggleView">
<attr name="switch_background" format="reference" />
<attr name="slide_button" format="reference" />
<attr name="switch_state" format="boolean" />
</declare-styleable>
/**
* 用于在xml里使用, 可指定自定义属性, 如果指定了样式, 则走此构造函数
* @param context
* @param attrs
* @param defStyle
*/
public ToggleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 获取配置的自定义属性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ToggleView, defStyle, 0);
int switchBackgroundResource = a.getResourceId(R.styleable.ToggleView_switch_background, -1);
int slideButtonResource = a.getResourceId(R.styleable.ToggleView_slide_button, -1);
mSwitchState = a.getBoolean(R.styleable.ToggleView_switch_state, false);
//获取背景图片和开关图片后设置图片,便于在onMeasure()方法中设置View宽和高,防止Null
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
init();
}

2.自定义ToggleView集成View后,在XML文件里不要忘记添加命名空间

“xmlns:cb=”http://schemas.android.com/apk/res-auto””

然后将自定义View的完整路径粘贴到XML中,这点类似于Android v4包下的ViewPager控件

以下便是demo中XML文件代码:

设置开关背景图片

- cb:switch_background=”@drawable/switch_background”

设置开关按钮图片

- cb:slide_button=”@drawable/slide_button”

设置开关默认状态

- cb:switch_state=”false”

3.界面中找到该控件, 设置初始信息

在Activity中通过findViewById方法找到自定义的View控件,和系统的组件操作没区别。

private ToggleView toggleView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleView = (ToggleView) findViewById(R.id.toggleView);
// toggleView.setSwitchBackgroundResource(R.drawable.switch_background);
// toggleView.setSlideButtonResource(R.drawable.slide_button);
// toggleView.setSwitchState(true);
//
// 设置开关更新监听
toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();
}
});
}

4.根据需求绘制界面内容

已经通过onMeasure()方法设置了View的宽度和高度,下面开始绘制的操作就全部在onDraw()方法中进行,onDraw(Canvas canvas) 方法中canvas参数:画布, 画板. 在上边绘制的内容都会显示到界面上.

// 根据开关状态boolean, 直接设置图片位置
if(mSwitchState){// 开
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 关
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}

开关打开时,开关按钮的位置在开关背景中的位置计算:

int newLeft = switchBackgroupBitmap.getWidth() - 

slideButtonBitmap.getWidth(); 背景的宽度-按钮的宽度就是当前开关按钮所在的X轴上的位置点

开关关闭时,当前开关按钮所在的X轴上的位置点=0

5.响应用户的触摸事件

在完成以上3步操作后,你会发现,只有在第一次进入后XML初始化默认开关状态的boolean值才会有变化,此后点击是没有任何效果的,这个时候我们就要想办法监听手势事件,重写onTouchEvent(MotionEvent event)方法,相信大多数朋友对这个方法并不陌生。

MotionEvent有三种状态:

MotionEvent.ACTION_DOWN: //按下屏幕
MotionEvent.ACTION_MOVE: //手指在屏幕上移动
MotionEvent.ACTION_UP //离开屏幕

当前需要考虑的问题是:

当手指按下屏幕后MotionEvent.ACTION_DOWN(在当前开关背景View中)开关的X轴位置应该移动到手指按下的位置;

当手指在屏幕上移动MotionEvent.ACTION_MOVE(在当前开关背景View中)开关按钮X轴应该随着手指移动的位置改变;

当手指离开屏幕后MotionEvent.ACTION_UP(在当前开关背景View中)开关按钮应该判断手指离开的位置是否是当前背景的一半位置,如果X轴位置大于View背景宽度的1/2、那么应该处于打开状态,如果X轴位置小于View背景宽度的1/2,那么应该处于关闭状态。

如图所示:

private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 重写触摸事件, 响应用户的触摸.
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根据当前按下的位置, 和控件中心的位置进行比较.
boolean state = currentX > center;
// 如果开关状态变化了, 通知界面. 里边开关状态更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 状态传出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
default:
break;
}
// 重绘界面
invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新
return true; // 消费了用户的触摸事件, 才可以收到其他的事件.
}

注意:

以上监听onTouchEvent(MotionEvent

event)方法后还存在一个问题,不知道大家有没有发现,我们没有设置开关按钮的边界值,什么意思呢?就是手指滑动的时候左边和右边可以画出当前背景之外。

所以这里需要对左右两边的X轴位置进行处理:

// Canvas 画布, 画板. 在上边绘制的内容都会显示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 绘制背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
// 2. 绘制滑块
if(isTouchMode){
// 根据当前用户触摸到的位置画滑块
// 让滑块向左移动自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;
int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
// 限定滑块范围
if(newLeft < 0){
newLeft = 0; // 左边范围
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右边范围
}
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根据开关状态boolean, 直接设置图片位置
if(mSwitchState){// 开
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 关
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}
}

6.创建一个状态更新监听.

基本上所以工作已经完成,这样我们一个自定义View已经大功告成了,当你完成这个效果后,你可能会发现有点类似于CheckBox。既然类似于CheckBox,我们知道当CheckBox点击选中和取消选中的时候都会有ischecked()方法来获取选中状态,所以我们这个自定义的开关按钮自然不能少这个功能,否则我们在界面上只有效果展示,却没有逻辑处理的地方。

public interface OnSwitchStateUpdateListener{
// 状态回调, 把当前状态传出去
void onStateUpdate(boolean state);
}
public void setOnSwitchStateUpdateListener(
OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}

代码很简单,写一个接口,然后定义一个回调方法返回开关状态,需要注意的是,在手指离开屏幕的时候,我们需要判断此次操作是否改变了开关的状态,如果没有变化我们不做操作,如果跟上次状态不同,则通知Activity状态更改!

case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根据当前按下的位置, 和控件中心的位置进行比较.
boolean state = currentX > center;
// 如果开关状态变化了, 通知界面. 里边开关状态更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 状态传出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;

Activity中回调也是非常简单的,类似于Android系统为我们提供的setOnClickListener回调接口,之前一直用系统定义的监听接口,这次通过简单的一个自定义View,我们也可以给自己的View写回调接口了。是不是觉得是件很开心的事情呢?

// 设置开关更新监听
toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();
}
});

以上所述是小编给大家介绍的Android自定义View实现开关按钮,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Android自定义View制作动态炫酷按钮实例解析

    普通按钮也就那么几种样式,看着都审美疲劳,先放效果图: 你会不会以为这个按钮是集结了很多动画的产物,我告诉你,并没有.所有的实现都是基于自定义View,采用最底层的onDraw一点一点的画出来的.没有采用一丁点的动画.虽然演示时间很短,但是要完成这么多变化,还是挺吃力. 首先讲解用法: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState)

  • Android自定义View之圆形进度条式按钮

    介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实

  • 自定义滑动按钮为例图文剖析Android自定义View绘制

    自定义View一直是横在Android开发者面前的一道坎. 一.View和ViewGroup的关系 从View和ViewGroup的关系来看,ViewGroup继承View. View的子类,多是功能型的控件,提供绘制的样式,比如imageView,TextView等,而ViewGroup的子类,多用于管理控件的大小,位置,如LinearLayout,RelativeLayout等,从下图可以看出 从实际应用中看,他们又是组合关系,我们在布局中,常常是一个ViewGroup嵌套多个ViewGro

  • Android自定义View实现拖动选择按钮

    本文为大家分享了Android实现拖动选择按钮的具体代码,供大家参考,具体内容如下 效果图 View代码 第一步:自定义属性 <declare-styleable name="DragView"> <attr name="icon_drag" format="reference"/> <attr name="color_circle" format="color"/> &

  • Android自定义View实现开关按钮

    前言:Android自定义View对于刚入门乃至工作几年的程序员来说都是非常恐惧的,但也是Android进阶学习的必经之路,平时项目中经常会有一些苛刻的需求,我们可以在GitHub上找到各种各样的效果,能用则用,不能用自己花功夫改改也能草草了事.不过随着工作经验和工作性质,越来越觉得自定义View是时候有必要自己花点功夫研究一下. 一.经过这两天的努力,自己也尝试着写了一个Demo,效果很简单,就是开关按钮的实现. 可能有的人会说这效果so easy,找UI切三张图就完事了,何必大费周折自定义.

  • Android自定义view实现阻尼效果的加载动画

    效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动.衰减振动.[1] 不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来.这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运

  • Android 自定义View 密码框实例代码

    暴露您view中所有影响可见外观的属性或者行为. •通过XML添加和设置样式 •通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 效果图展示: 支持的样式 可以通过XML定义影响外边和行为的属性如下 边框圆角值,边框颜色,分割线颜色,边框宽度,密码长度,密码大小,密码颜色 <declare-styleable name="PasswordInputView"> <attr name="borde

  • Android自定义View详解

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 [ 3.重写onMesure ] 4.重写onDraw 我把3用[]标出了,所以说3不一

  • Android自定义View中attrs.xml的实例详解

    Android自定义View中attrs.xml的实例详解 我们在自定义View的时候通常需要先完成attrs.xml文件 在values中定义一个attrs.xml 然后添加相关属性 这一篇先详细介绍一下attrs.xml的属性. <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定义公共属性 <attr name="titleText" for

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android自定义View绘制随机生成图片验证码

    本篇文章讲的是Android自定义View之随机生成图片验证码,开发中我们会经常需要随机生成图片验证码,但是这个是其次,主要还是想总结一些自定义View的开发过程以及一些需要注意的地方. 按照惯例先看看效果图: 一.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure 4.重写onDraw 其中onMesure方法不一定要重写,但大部分情况下还是需要重写的 二.View 的几个构造函数 1.public CustomV

  • Android自定义View绘制的方法及过程(二)

    上一篇<Android 自定义View(一) Paint.Rect.Canvas介绍>讲了最基础的如何自定义一个View,以及View用到的一些工具类.下面讲下View绘制的方法及过程 public class MyView extends View { private String TAG = "--------MyView"; private int width, height; public MyView(Context context, AttributeSet a

  • Android自定义View实现等级滑动条的实例

     Android自定义View实现等级滑动条的实例 实现效果图: 思路: 首先绘制直线,然后等分直线绘制点: 绘制点的时候把X值存到集合中. 然后绘制背景图片,以及图片上的数字. 点击事件down的时候,换小图片为大图片.move的时候跟随手指移动. up的时候根据此时的X计算最近的集合中的点,然后自动吸附回去. 1,自定义属性 <?xml version="1.0" encoding="utf-8"?> <resources> <de

  • Android自定义View实现loading动画加载效果

    项目开发中对Loading的处理是比较常见的,安卓系统提供的不太美观,引入第三发又太麻烦,这时候自己定义View来实现这个效果,并且进行封装抽取给项目提供统一的loading样式是最好的解决方式了. 先自定义一个View,继承自LinearLayout,在Layout中,添加布局控件 /** * Created by xiedong on 2017/3/7. */ public class Loading_view extends LinearLayout { private Context m

随机推荐