欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义View实现开关按钮

程序员文章站 2024-03-03 20:27:04
 前言:android自定义view对于刚入门乃至工作几年的程序员来说都是非常恐惧的,但也是android进阶学习的必经之路,平时项目中经常会有一些苛刻的需求,我...

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

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

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

Android自定义View实现开关按钮

自定义控件

1.为什么自定义view?

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

2.android 的界面绘制流程?

Android自定义View实现开关按钮 

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”

Android自定义View实现开关按钮

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,那么应该处于关闭状态。

如图所示:

Android自定义View实现开关按钮

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实现开关按钮,希望对大家有所帮助