Android自定义控件之开关按钮学习笔记分享
今天来讲讲自定义单个控件,就拿开关按钮来讲讲,相信大家见了非常多这样的了,先看看效果:
我们可以看到一个很常见的开关按钮,那就来分析分析。
首先:
这是由两张图片构成:
①一张为有开和关的背景图片
②一张为控制开和关的滑动按钮
第一步:
写个类继承view,并重写几个方法:
第一个为构造函数,重写一个参数的函数和两个参数的函数就够了,因为两个参数的函数能够使用自定义属性
第二个为控制控件的大小–>protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {}
第三个为绘制控件的方法–>protected void ondraw(canvas canvas) {}
第二步:
将用户指定的两张图片加载进来,这里使用自定义属性加载, 在values目录下新建attrs.xml,在xml文件中指定自定义属性名和自定义属性的字段及值类型(即背景图和滑块图)即可:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="switchview_attrs"> <attr name="background" format="reference"></attr> <attr name="slide" format="reference"></attr> </declare-styleable> </resources>
各个字段的含义我在这就不讲了,不懂的就去看看前几篇《android开发笔记之自定义组合控件》有讲过,写好就只需在构造函数中加载进来
public switchview(context context, attributeset attrs) { super(context, attrs); //拿到自定义属性 typedarray ta = context.obtainstyledattributes(attrs, r.styleable.switchview_attrs); //拿到自定义字段的值 drawable switchbackground = ta.getdrawable(r.styleable.switchview_attrs_background); drawable switchview_slide = ta.getdrawable(r.styleable.switchview_attrs_slide); //把值设置到相应组件上 backgroundbitmap = convertdrawable2bitmapsimple(switchbackground); switchslide = convertdrawable2bitmapsimple(switchview_slide); }
不过要注意的是:
因为从自定义属性中取到的是drawable对象,而我们要的是一个bitmap对象,所以我们先得把drawable对象转成bitmap对象,其实有很多种方法来转,这里就介绍种最简单的方法,借助与bitmapdrawable类:
//将drawable转成bitmap public bitmap convertdrawable2bitmapsimple(drawable drawable) { bitmapdrawable bd= (bitmapdrawable)drawable; return bd.getbitmap(); }
第三步:
onmeasure方法来控制控件的大小,我们可以看到这个开关按钮的大小就跟背景的大小一样大,只需要设置为背景的大小:
//控制控件的大小 @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { if (backgroundbitmap != null) { //控件大小设置为背景的大小 setmeasureddimension(backgroundbitmap.getwidth(), backgroundbitmap.getheight()); }else { super.onmeasure(widthmeasurespec, heightmeasurespec); } }
第四步:
既然这个开关按钮需要通过点击或移动来控制控件的开和关,所以就需要实现ontouchevent方法,当然应该有三个事件会触发:按下、移动和抬起的时候,每次点击、移动或抬起都需要重绘,我们先来分析下滑块的状态有哪些(应该有四种状态)一开始默认状态为空:
1.点击的时候
2.移动的时候
3.抬起的时候
4.空的时候(即什么都没干的时候)
先分析下点击的时候的情况:
①当按下或移动的坐标大于滑块宽度一半时将滑块右移
②当按下或移动的坐标小于滑块宽度一半时滑块不动
注:防止滑块移至背景外面,最大是滑块右边和背景右边对齐(即最大离左边为背景宽度-滑块宽度)
再来看看移动的时候的情况应该是和点击的时候是一样的。
再来分析抬起的时候的情况:
①如果开关状态是打开的就将滑块移动至右边
②如果开关状态是关闭的就将滑块移动至左边
那怎么判断什么时候是打开状态和关闭状态呢??
①抬起的坐标大于背景宽度一半的时候设为打开状态
②抬起的坐标小于背景宽度坐标一 半的时候设为关闭状态
再来分析下空的时候,可以发现它和抬起的时候的情况是一样的。
第五步:
在ondraw方法中将背景和滑块绘制出来。刚才分析了ontouchevent方法,这次是一样的,滑块的四个状态分别处理,前面ontouchevent方法中滑块的状态改变,然后通过invalidate()方法来通知系统重绘。
第六步:
我们做这个自定义控件是为了让用户使用的,现在这个是没有什么用的,用户用不了,所以可以通过设置监听器来对外提供接口。
/** * switchview开关监听接口 * * */ interface onswitchchangedlistener { public void onswitchchange(boolean isopen); } /** * 设置 switchview状态监听 * */ public void setonchangelistener(onswitchchangedlistener listener) { switchlistener = listener; }
这个监听器中的boolean值需要赋值,那在什么时候赋值呢,应该是在抬起或空的状态的时候给它赋值,因为那个时候才真正确定开关按钮是打开的还是关闭的。
第七步:
到这一步就是来使用了,在布局文件中把自定义的这个控件定义出来
<com.example.custom.switchview minguo:background="@drawable/switch_background" minguo:slide="@drawable/slide_button_background" android:id="@+id/switchview" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
使用定义好的view应该都会用了,不会的去看看《android开发笔记之自定义组合控件》。
核心代码:
布局文件
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:minguo="http://schemas.android.com/apk/res/com.example.custom" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.custom.mainactivity" > <com.example.custom.switchview minguo:background="@drawable/switch_background" minguo:slide="@drawable/slide_button_background" android:id="@+id/switchview" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </linearlayout>
自定义view: switchview.java
/** * 自定义开关按钮 * @author administrator * */ public class switchview extends view { //背景图片和滑块图片 private bitmap backgroundbitmap,switchslide; //画笔 private paint paint; //得到的x坐标(点击、移动、抬起) private float currentx; //判断开关是否打开的标记位 private boolean isopen = false; //开关打开与关闭的监听器 private onswitchchangedlistener switchlistener; //滑块的四种状态 public static final int state_down = 1; //按下的时候 public static final int state_move = 2; //移动的时候 public static final int state_up = 3; //抬起的时候 public static final int state_none = 0; //空的时候(即什么都没干的时候) //标记状态(默认为空状态) private int state = state_none; public switchview(context context) { super(context,null); } public switchview(context context, attributeset attrs) { super(context, attrs); //拿到自定义属性 typedarray ta = context.obtainstyledattributes(attrs, r.styleable.switchview_attrs); //拿到自定义字段的值 drawable switchbackground = ta.getdrawable(r.styleable.switchview_attrs_background); drawable switchview_slide = ta.getdrawable(r.styleable.switchview_attrs_slide); //把值设置到相应组件上 backgroundbitmap = convertdrawable2bitmapsimple(switchbackground); switchslide = convertdrawable2bitmapsimple(switchview_slide); } //将drawable转成bitmap public bitmap convertdrawable2bitmapsimple(drawable drawable) { bitmapdrawable bd = (bitmapdrawable)drawable; return bd.getbitmap(); } //控制控件的大小 @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { if (backgroundbitmap != null) { //控件大小设置为背景的大小 setmeasureddimension(backgroundbitmap.getwidth(), backgroundbitmap.getheight()); }else { super.onmeasure(widthmeasurespec, heightmeasurespec); } } //绘制控件的样式 @override protected void ondraw(canvas canvas) { //背景为空的时候才将背景绘制 if (backgroundbitmap != null) { paint = new paint(); //第一个参数表示需要画的bitmap //第二个参数表示bitmap左边离控件的左边距 //第三个参数表示bitmap上边离控件的上边距 //第四个参数表示画笔 canvas.drawbitmap(backgroundbitmap, 0, 0, paint); } switch (state) { case state_down: //按下和移动的触发事件都一样,都是将滑块移动 case state_move: //当按下或移动的坐标大于滑块宽度一半时将滑块右移 if (currentx > switchslide.getwidth()/2f) { //让滑块向右滑动(重新绘制滑块的位置) float left = currentx - switchslide.getwidth()/2f; //防止滑块移至背景外面,最大是滑块右边和背景右边对齐(即最大离左边为背景宽度-滑块宽度) float maxleft = backgroundbitmap.getwidth() - switchslide.getwidth(); if (left > maxleft) { left = maxleft; } canvas.drawbitmap(switchslide, left, 0, paint); //当按下或移动的坐标小于滑块宽度一半时滑块不动 }else if (currentx < switchslide.getwidth()/2f) { //让滑块不动就可以了 canvas.drawbitmap(switchslide, 0, 0, paint); } break; case state_none: //空或抬起的时候将滑块至于左边或右边 case state_up: //如果是打开的将滑块移动至右边,并将打开状态传至监听器 if (isopen) { if (switchlistener != null) { switchlistener.onswitchchange(true); } canvas.drawbitmap(switchslide, backgroundbitmap.getwidth() - switchslide.getwidth(), 0, paint); //如果是关闭的将滑块至于左边,并将关闭状态传至监听器 }else { if (switchlistener != null) { switchlistener.onswitchchange(false); } canvas.drawbitmap(switchslide, 0, 0, paint); } break; default: break; } } @override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { case motionevent.action_down: currentx = event.getx(); //将标记位修改成按下的状态 state = state_down; //通知系统重新绘制界面 invalidate();//在主线程 // postinvalidate();//在子线程 break; case motionevent.action_move: currentx = event.getx(); //将标记位修改为移动状态 state = state_move; invalidate(); break; case motionevent.action_up: currentx = event.getx(); //将标记为修改为抬起状态 state = state_up; //抬起的坐标大于背景宽度一半的时候设为打开状态 if (currentx > backgroundbitmap.getwidth()/2f) { //滑块在右边,开启 isopen = true; //抬起的坐标小于背景宽度坐标一 半的时候设为关闭状态 }else if (currentx < backgroundbitmap.getwidth()) { //滑块在左边,关闭 isopen = false; } invalidate(); break; } return true; } /** * switchview开关监听接口 * * */ interface onswitchchangedlistener { public void onswitchchange(boolean isopen); } /** * 设置 switchview状态监听 * */ public void setonchangelistener(onswitchchangedlistener listener) { switchlistener = listener; } }
mainactivity.java
public class mainactivity extends activity { private switchview switchview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); switchview = (switchview) findviewbyid(r.id.switchview); switchview.setonchangelistener(new onswitchchangedlistener() { @override public void onswitchchange(boolean isopen) { if (isopen) { //打开开关的时候的逻辑 toast.maketext(mainactivity.this, "开关打开了", toast.length_long).show(); }else { //关闭开关的时候的逻辑 toast.maketext(mainactivity.this, "开关关闭了", toast.length_long).show(); } } }); } }
大家看起来这么简单的一个写了这么多,其实我们学习这个不是为了写这个,比这个好的开源多的是,而是为了学习这种思路与思维,大家赶紧试试吧!
谢谢大家的阅读,也希望大家可以继续关注的更多精彩内容。
上一篇: 详解Spring Boot 事务的使用
推荐阅读