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();
        }
      }
    });
  }
}

大家看起来这么简单的一个写了这么多,其实我们学习这个不是为了写这个,比这个好的开源多的是,而是为了学习这种思路与思维,大家赶紧试试吧!

谢谢大家的阅读,也希望大家可以继续关注我们的更多精彩内容。

(0)

相关推荐

  • Android自定义控件实现温度旋转按钮效果

    首先看下效果图 温度旋转按钮 实现思路 初始化一些参数 绘制刻度盘 绘制刻度盘下的圆弧 绘制标题与温度标识 绘制旋转按钮 绘制温度 处理滑动事件 提供一些接口方法 实现方法 初始化一些参数 public class TempControlView extends View { // 控件宽 private int width; // 控件高 private int height; // 刻度盘半径 private int dialRadius; // 圆弧半径 private int arcRa

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

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

  • Android如何自定义按钮效果

    安卓原生的按钮是多么丑,效果是多么单调,大家也是有目共睹的. 要做一个APP少不了使用按钮,一个好看的按钮少不了好看的效果和外表,这次主要跟大家讲讲如何用drawable的xml文件弄一些好看的自定义样式. 首先是外表 在APP中四四方方,灰色底黑色字的按钮是很难看的,我们希望看到的是圆角,彩色,白字(根据你的个人审美也可以是其他样式). 首先是在layout里新建一个按钮 ,然后在drawable文件夹里新建一个drawable resource file ,不妨起名为shape ,加一个se

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

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

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

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

  • Android自定义实现开关按钮代码

    我们在应用中经常看到一些选择开关状态的配置文件,做项目的时候用的是android的Switch控件,但是感觉好丑的样子子 个人认为还是自定义的比较好,先上个效果图: 实现过程: 1.准备开关不同状态的两张图片放入drawable中. 2.xml文件中添加代码: <ToggleButton android:id="@+id/switch1" android:layout_width="wrap_content" android:layout_height=&qu

  • Android 自定义按钮点击事件和长按事件对比

     Android 自定义按钮点击事件和长按事件对比 一个按钮同时实现点击和长按事件,有时候会有冲突,我们针对这一现象来自定义按钮来区分点击和长按事件 1.xml中 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="mat

  • Android自定义控件之开关按钮学习笔记分享

    今天来讲讲自定义单个控件,就拿开关按钮来讲讲,相信大家见了非常多这样的了,先看看效果: 我们可以看到一个很常见的开关按钮,那就来分析分析. 首先: 这是由两张图片构成: ①一张为有开和关的背景图片 ②一张为控制开和关的滑动按钮 第一步: 写个类继承View,并重写几个方法: 第一个为构造函数,重写一个参数的函数和两个参数的函数就够了,因为两个参数的函数能够使用自定义属性 第二个为控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int

  • JavaWeb学习笔记分享(必看篇)

    自定义列表 <dl></dl>:表示列表的范围 **在里面 <dt></dt>:上层内容 **在里面 <dd></dd>:下层内容 有序列表 <ol></ol>:有序列表的范围 --属性 type:设置排序方式,1(默认),a,i.. **在ol标签里面 <li>具体内容</li> 无序列表 <ul></ul>:无序列表的范围 --属性 type:circle(空

  • Android开发艺术探索学习笔记(七)

    第七章 Android动画深入分析 Android的动画分为三种:View动画,帧动画,属性动画.帧动画属于View动画. 7.1 View动画 View动画的作用对象是View,共有四种动画效果:平移(Translate),缩放(Scale),旋转(Rotate),透明度(Alpha). 7.1.1 View动画的种类 View动画的保存路径:res/anim/filename.xml.XML格式语法如下: <?xml version="1.0" encoding="

  • Java中反射的学习笔记分享

    目录 简介 一个简单的例子 设置使用反射 模拟instanceof运算 了解类的方法 获取有关构造函数的信息 查找类字段 按名称调用方法 创建新对象 更改字段的值 使用数组 总结 简介 反射是Java编程语言中的一个特性.它允许执行的Java程序检查或 操作 自身,并操作程序的内部属性.例如,Java类可以获取其所有成员的名称并显示它们. 从程序内部检查和操作Java类的能力听起来可能不太显示,但是在其他编程语言中,这个特性根本不存在.例如,在C或C ++ 程序中无法获取有关该程序中定义的函数的

  • Android自定义控件之组合控件学习笔记分享

    我们来讲一下自定义组合控件,相信大家也接触过自定义组合控件吧,话不多说,直接干(哈~哈~): 大家看到这个觉得这不是很简单的吗,这不就是写个布局文件就搞定嘛,没错,确实直接上布局就行,不过,我只是用这个简单的例子来讲一下自定义组合控件的用法. 首先看看,这一行行的条目看起来都长得差不多,只是图片和文字不一样,没错,就是看中这一点,我们可以把一个条目做成一个组合控件,做为一个整体,这样不管你有几个条目,就写几个组合控件就行了. 步骤: 1.先建立组合控件的布局 myView.xml <Relati

  • Bootstrap选项卡学习笔记分享

    本文实例为大家分享了Bootstrap选项卡的学习笔记,供大家参考,具体内容如下 tab选项卡 <body> <div class="container"> <!-- tab选项卡 --> <ul class="nav nav-tabs"> <li class="active"><a href="#pan1" data-toggle="tab"

  • Bootstrap教程JS插件滚动监听学习笔记分享

    本文主要来学习一下JavaScript插件--滚动监听. 1.案例 滚动监听插件可以根据滚动条的位置自动更新所对应的导航标记.你可以试试滚动这个页面,看看左侧导航的变化. 先把实现的代码上了,你可以通过测试代码先来看看效果. <!DOCTYPE html> <html> <head> <title>Bootstrap</title> <meta name="viewport" content="width=de

  • Android仿优酷圆形菜单学习笔记分享

    先来看看效果: 首先来分析一下: 这个菜单可以分成三个菜单: 1.一级菜单(即最内圈那个菜单) 2.二级菜单(即中间圈那个菜单) 3.三级菜单(即最外圈那个菜单) 首先,可以将这三个菜单使用相对布局 一级菜单只有一个按钮(即home),可以控制二级和三级菜单 二级菜单有三个按钮(即menu),中间那个按钮可以控制三级菜单 三级菜单有七个按钮 那先把布局文件先写出来,采用三个相对布局(即每个菜单采用一个相对布局) <RelativeLayout xmlns:android="http://s

  • Android手势密码view学习笔记(一)

    刚接触Android的时候看到别人写的手势密码view,然后当时就在想,我什么时候才能写出如此高端的东西?? 没关系,不要怕哈,说出这样话的人不是你技术不咋地而是你不愿意花时间去研究它,其实也没有那么难哦(世上无难事,只怕有心人!),下面我们就一步一步实现一个手势密码view. 想必都看过手势密码view,但我们还是看看我们今天要实现的效果吧: 上面是一个手势view的提示view,下面是一个手势view. 用法: <com.leo.library.view.GestureContentView

  • Android手势密码view学习笔记(二)

    我们还是接着我们上一篇博客中的内容往下讲哈,上一节 Android手势密码view笔记(一)我们已经实现了我们的IndicatorView指示器view了: 下面我们来实现下我们的手势密码view: 实现思路: 1.我们照样需要拿到用户需要显示的一些属性(行.列.选中的图片.未选中的图片.错误显示的图片.连接线的宽度跟颜色......). 2.我们需要根据手势的变换然后需要判断当前手指位置是不是在某个点中,在的话就把该点设置为选中状态,然后每移动到两个点(也就是一个线段)就记录该两个点. 3.最

随机推荐