文档章节

android开发笔记之自定义开关按钮

Mr_Nice
 Mr_Nice
发布于 2016/05/22 20:01
字数 2301
阅读 6
收藏 0
点赞 2
评论 0

今天来讲讲自定义单个控件,就拿开关按钮来讲讲,相信大家见了非常多这样的了,先看看效果:

这里写图片描述

我们可以看到一个很常见的开关按钮,那就来分析分析。

首先:

这是由两张图片构成:

①一张为有开和关的背景图片

②一张为控制开和关的滑动按钮

第一步:

写个类继承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();
                }
            }
        });
    }
}

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

© 著作权归作者所有

共有 人打赏支持
Mr_Nice
粉丝 0
博文 47
码字总数 39947
作品 0
广州
Android动画:献上一份详细 & 全面的动画知识学习攻略

前言 动画的使用 是 开发中常用的知识 可是动画的种类繁多、使用复杂,每当需要 采用自定义动画 实现 复杂的动画效果时,很多开发者就显得束手无策 本文将献上一份动画的全面介绍攻略,包括动...

Carson_Ho ⋅ 06/06 ⋅ 0

彻底理解 Android 中的阴影

如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范。一般而言,Material Design 是一个包含光线,材质和投影的三维环境。如果我们想要在 App 的开发过程中,...

丁佳辉 ⋅ 04/18 ⋅ 0

物联网实验4 alljoyn物联网实验之手机局域网控制设备

  AllJoyn开源物联网协议框架,官方描述是一个能够使连接设备之间进行互操作的通用软件框架和系统服务核心集,也是一个跨制造商来创建动态近端网络的软件应用。高通已经将该项目捐赠给了一...

ai物联网 ⋅ 2014/11/12 ⋅ 0

Android 动画:这是一份详细 & 清晰的 动画学习指南

前言 动画的使用 是 开发中常用的知识 可是动画的种类繁多、使用复杂,每当需要 采用自定义动画 实现 复杂的动画效果时,很多开发者就显得束手无策 本文将献上一份动画的全面介绍攻略,包括动...

Carson_Ho ⋅ 05/03 ⋅ 0

(原创) alljoyn物联网实验之手机局域网控制设备

AllJoyn开源物联网协议框架,官方描述是一个能够使连接设备之间进行互操作的通用软件框架和系统服务核心集,也是一个跨制造商来创建动态近端网络的软件应用。高通已经将该项目捐赠给了一个名...

ai物联网 ⋅ 2014/11/12 ⋅ 0

自动化构建Android项目 ---- Jenkins自动化部署学习笔记(二)

  上篇文章跟大家分享了在Windows上安装Jenkins的方法,这篇文章来跟大家分享一下利用Jenkins自动化构建Android项目: 一、所需准备: Android项目上传至版本管理平台,这里我准备了Githu...

onestravel ⋅ 05/22 ⋅ 0

Android开发权威指南(第2版)新书发布

《Android开发权威指南(第二版)》是畅销书《Android开发权威指南》的升级版,内容更新超过80%,是一本全面介绍Android应用开发的专著,拥有45章精彩内容供读者学习。  《Android开发权威指...

androidguy ⋅ 2013/09/05 ⋅ 0

Android Hybrid开发:这是一份详细 & 全面的WebView学习攻略

前言 现在很多里都内置了Web网页(),比如说很多电商平台,淘宝、京东、聚划算等等,如下图 那么这种该如何实现呢?其实这是里一个叫组件实现 今天,我将献上一份全面 & 详细的 攻略,含具体...

Carson_Ho ⋅ 06/19 ⋅ 0

腾讯X5WebView集成2018-05-15

工作中经常偶尔会用到H5网页来加载页面,使用原生的Android的WebView可以加载,但是当网页内容比较多的时候,需要等待很久才能加载完,加载完后用户才能看到网页中的内容,这样用户需要等很久...

林灬 ⋅ 05/15 ⋅ 0

Android性能优化:那些不可忽略的绘制优化

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的绘制优化,希望你们会喜欢。 目录 // 方式2:在 BaseActivity 的 onCreate() 方法中使用下面的代码移除 优化方案2:移除 控件...

Carson_Ho ⋅ 05/21 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

对于程序员的招聘问题,作为软件人的一些吐槽和建议

作为软件人,找工作有时候似乎挺苦逼的。 说真的,让我去掉前面这句中“似乎”二字吧。就是苦逼!很多人都曾抱怨处在招聘的一方很糟糕——我们没有任何可靠的方式来甄别会写代码并且写得好的...

老道士 ⋅ 7分钟前 ⋅ 0

HDFS原理学习

一、概述 1、 Hadoop整合了众多的文件系统,首先提供了一个高层的文件系统抽象org.apache.hadoop.fs.FileSystem。然后有各个文件系统的实现类。 2、Hadoop是JAVA编写的,不同文件系统之间的交...

cjxcloud ⋅ 10分钟前 ⋅ 0

Linux下MySQL表名不区分大小写的设置方法(抄袭别人的)

Linux下MySQL表名不区分大小写的设置方法 MySQL表名不区分大小写的设置方法 在用centox安装mysql后,把项目的数据库移植了过去,发现一些表的数据查不到,排查了一下问题,最后发现是表名的大...

随风而浮沉 ⋅ 16分钟前 ⋅ 0

ubuntu下安装宋体simsun

sudo cp simsun.ttc /usr/share/fonts cd /usr/share/fonts sudo chmod 644 simsun.ttc 更新字体缓存: 代码: sudo mkfontscale 代码: sudo mkfontdir 代码: sudo fc-cache -fsv 安装chrome扩......

wangxuwei ⋅ 17分钟前 ⋅ 0

利用 ssh 传输文件

Linux 下一般可以用 scp 命令通过 ssh 传送文件: #把服务器上的 /home/user/a.txt 发送到本机的 /var/www/local_dir 目录下scp username@servername:/home/user/a.txt /var/www/local_dir...

大灰狼时间 ⋅ 27分钟前 ⋅ 0

web3j教程:android和java程序员如何使用web3j开发区块链以太坊

如何使用web3j为Java应用或Android App增加以太坊区块链支持,本教程内容即涉及以太坊中的核心概念,例如账户管理包括账户的创建、钱包创建、交易转账,交易与状态、智能合约开发与交互、过滤...

智能合约 ⋅ 49分钟前 ⋅ 0

web3j开发java或android以太坊智能合约快速入门

web3j简介 web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 可以通过它进行以太坊区块链...

笔阁 ⋅ 51分钟前 ⋅ 0

一起读书《深入浅出nodejs》-异步I/O

异步I/O “异步”这个名词其实很早就诞生了,但它大规模流行却是在Web 2.0浪潮中,它伴随着AJAX的第一个A(Asynchronous)席卷了Web。 为什么要异步I/O 关于异步I/O为何在Node里如此重要,这与...

小草先森 ⋅ 54分钟前 ⋅ 0

JVM各种问题

1、如果启动什么都不设,会怎样? 先来看一个命令 [root@localhost bin]# java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=29899008 -XX:MaxHeapSize=478384128 -XX:+PrintCo......

算法之名 ⋅ 今天 ⋅ 0

SAS笔记-宏2

宏是一种文本,一般来说其编译是在程序执行之前。 宏变量的创建 %let语句 %let macro_variables = text; %let是常见的宏变量建立方式,其编译就在执行前。如下例中,想要宏变量test等于数据集...

tonorth123 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部