Android开发技巧——自定义控件之增加状态

题外话

这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天。不想把这篇拖到下个月,所以还是先硬着头皮写了。

《自定义控件》我也没想着要写多长或多短,只是介绍点知识,大部分都是自己积累和摸索的。网上有很多教如何自定义某某控件来做出很酷炫的效果的,但我想写的与这些不同。我平时所积累下来的知识,还是偏向于实用而匮乏于酷炫。而对于各种酷炫的效果,其实也是说不完写不尽的。掌握一定的数学及算法知识,再加上对API的了解,基本上你觉得能实现的你都能够去实现。而我这里,只是想着介绍些相关的基础知识。这些知识,若与只是使用控件来搭建界面相比,它们算是进阶,但对于想要进阶的开发者而言,它们都是基础。

写完这篇之后,我可能会写一两篇自定义控件来优化界面的具体例子,也兑现我在第一篇的最后所说的话,但不会对onDraw讲得太细。

还有就是Gradle的翻译我也拖了好久了,现在都到2.9版本了。目前我个人采用的翻译方式是使用CAT辅助翻译,生成编译之后的文件。之前是再采用人工合并的方式合完之后提交到Github同时同步到七牛空间。昨天花了点时间,写了段代码来进行译文和原文的自动合并,生成中英对照的HTML,到时就能把从合并内容所节省下来的时间用于翻译了。

正文

场景

View类是Android中的所有UI组件的基础构建模块,之前翻过某版本的源代码,大概有2万行左右的代码,它主要负责组件的绘制及事件的处理。我们在一些自定义控件的场合,可能需要在一个组件上画些东西,也是通过重写ViewonDraw方法,通过其参数的Canvas对象进行绘制。本篇先不谈onDraw,而是说一下另外一个点——状态。

在我们学习<selector/>的时候,就知道了关于一个视图组件会有许多种状态,比如按下(pressed),选择(selected),可用(enabled),正常状态,其他状态等等。View也处理了关于一个组件在不同状态下的显示的绘制逻辑,通常继承自View的组件都有着以上所说的这些状态。但是也有一些状态是View没有提供的,而我们可能正需要它们,所以就需要对状态进行扩展,增加我们的状态,比如增加checked

这里有一个具体的场景:



这是一个开关按钮,开关状态下背景不同,文字不同,文字旁边的图片也不同。状态我用checked,文字我定义了两个属性:onText以及offText,文字旁边的图片我打算只用一个foreground属性,但需要写一个selector来定义正常状态(未锁)和checked状态(锁定)下的图片。

实现

首先写一个类继承自TextView,因为我打算用TextViewsetCompoundDrawables来设定文字旁边的图片。

然后定义属性:

    <declare-styleable name="ToggleView">
        <attr name="android:foreground"/>
        <attr name="pwOnText"/>
        <attr name="pwOffText"/>
        <attr name="pwColor"/>
        <attr name="pwDrawableHeight"/>
    </declare-styleable>

读取属性:

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToggleView);
        stateListDrawable = (StateListDrawable) a.getDrawable(R.styleable.ToggleView_android_foreground);
        colorStateList = a.getColorStateList(R.styleable.ToggleView_pwColor);
        offText = a.getString(R.styleable.ToggleView_pwOffText);
        onText = a.getString(R.styleable.ToggleView_pwOnText);
        drawableHeight = a.getDimensionPixelSize(R.styleable.ToggleView_pwDrawableHeight, 0);
        a.recycle();

这些都是前几篇的内容。

我们通过在类中实现Checkable接口,可以完成选中的逻辑,但是画出来的状态却没有更新,所以接下来的实现过程就是本篇的主要内容:

首先定义状态集:

    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};

然后我们要把状态给加进去。我们需要重写protected int[] onCreateDrawableState(int extraSpace)方法,如下:

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

先调用父类的onCreateDrawableState方法得到状态数组对象drawableState,但是参数extraSpace要加上1,因为我们要往里面增加一个状态。然后判断在代码逻辑中,是否为选中状态,如果是的话,调用mergeDrawableStates(drawableState, CHECKED_STATE_SET)方法把我们的状态值给加进去,最终返回drawableState

但是我们虽然实现了Checkable接口,在设置状态时却没有触发到这个状态。所以我们需要自己去触发这个状态。

    @Override
    public void setChecked(boolean checked) {
        if (isChecked != checked) {
            isChecked = checked;
            refreshDrawableState();
        }
    }

在状态改变时,调用refreshDrawableState()刷新状态。

最后,我们要重写drawableStateChanged()方法,获取到当前状态的drawable,然后绘制出来。

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        if (stateListDrawable != null) {
            int[] myDrawableState = getDrawableState();
            stateListDrawable.setState(myDrawableState);
            Drawable drawable = stateListDrawable.getCurrent();
            if(drawableHeight != 0) {
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth() * drawableHeight / drawable.getIntrinsicHeight(), drawableHeight);
            } else {
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
            }
            if (isChecked) {
                setCompoundDrawables(drawable, null, null, null);
            } else {
                setCompoundDrawables(null, null, drawable, null);
            }
        }
        setText(isChecked ? onText : offText);
    }

这部分的逻辑其实没有我代码所写的那么复杂,代码中我需要设定drawable的大小,以及在不同的状态下设置drawable的位置,实际上逻辑只需要如下:

  1. 获取当前的drawableState状态
  2. 对stateListDrawable(带状态的drawable集)设置状态。
  3. 获取stateListDrawable的当前状态的drawable
  4. 进行你所想要的绘制。

这样就完成了。

总结

从上面可知,增加状态的过程如下:

  • 定义状态数组
  • 重写protected int[] onCreateDrawableState(int extraSpace)
  • 调用refreshDrawableState()
  • 重写protected void drawableStateChanged()

本篇的内容不多。另外,关于onCreateDrawableState方法的调用,我理解并不深入,如果描述有误,敬请指正。

本文原创,转载请注明CSDN博客上的出处: http://blog.csdn.net/maosidiaoxian/article/details/50112191

Android开发技巧——自定义控件之增加状态的更多相关文章

  1. Android开发技巧——自定义控件之使用style

    Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...

  2. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  3. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

  4. Android开发技巧——写一个StepView

    在我们的应用开发中,有些业务流程会涉及到多个步骤,或者是多个状态的转化,因此,会需要有相关的设计来展示该业务流程.比如<停车王>应用里的添加车牌的步骤. 通常,我们会把这类控件称为&quo ...

  5. Android开发技巧——实现可复用的ActionSheet菜单

    在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...

  6. Android开发技巧——高亮的用户操作指南

    Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...

  7. 50个android开发技巧

    50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html

  8. Android开发技巧——大图裁剪

    本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...

  9. Android开发技巧——使用PopupWindow实现弹出菜单

    在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...

随机推荐

  1. android推荐使用dialogFrament而不是alertDialog

    DialogFragment在android 3.0时被引入.是一种特殊的Fragment,用于在Activity的内容之上展示一个模态的对话框.典型的用于:展示警告框,输入框,确认框等等. 在Dia ...

  2. 用了一天的时间,linux下expect实现ssh自动登录服务器记,鄙视下网上各种抄来抄去残段子

    因为要对客户方的快30个项目进行特别有顺序的重启,所以不得不想办法写个脚本,网上看了不少段子.真是残缺的可以.没有一段是可以正常运行的.我来按顺序记录一下 脚本的本身 使用expect实现自动登录的脚 ...

  3. ubuntu opengl 开发

    开发环境: eclipse,需要安装C++开发插件,在自带的源中查找安装C++开发工具包即可 下载安装gl库: sudo apt-get install libgl1-mesa-dev 下载安装glu ...

  4. 安装解压版本的MySQL,安装过程中的常见命令,检查windows系统错误日志的方式来检查MySQL启动错误,关于Fatal error: Can't open and lock privilege

     以端口 port = 3306 # 设置mysql的安装目录 basedir=D://Installed//mysql-5.6.26-winx64//mysql-5.6.26-winx64 # ...

  5. 剑指Offer——全排列递归思路

    剑指Offer--全排列递归思路 前言 全排列,full permutation, 可以利用二叉树的遍历实现.二叉树的递归遍历,前中后都简洁的难以置信,但是都有一个共同特点,那就是一个函数里包含两次自 ...

  6. Java并发框架——公平性

    所谓公平性指所有线程对临界资源申请访问权限的成功率都一样,不会让某些线程拥有优先权.通过前面的CLH Node FIFO学习知道了等待队列是一个先进先出的队列,那么是否就可以说每条线程获取锁时就是公平 ...

  7. 数据库隔离级别(mysql+Spring)与性能分析

     数据库隔离级别与Spring配置事务的联系及性能影响,以下是个人理解,如果有瑕疵请及时指正.   这里以mysql为例,先明确以下几个问题: 一.一般项目如果不自己配置事务的话,一般默认的是au ...

  8. Dynamics CRM2013 附件禁用方案

    CRM2013的附件功能和以往有了不同,把公告.活动.注释合在了一块并称注释,在使用的过程中会发现一个无语的地方,就算表单状态为停用,注释还是处于可编辑状态,而且也查询不到公开的方法来处理注释的,为了 ...

  9. java设计模式---职责链模式

    职责链的本质:分离职责,动态组合 样例: /** * 定义职责对象的接口 * */ public abstract class Handler { protected Handler successo ...

  10. JAVA之旅(二十)—HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习

    JAVA之旅(二十)-HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习 我们继续说一下集合框架 Set:元素是无序(存入和取出的顺序不一定 ...