Android开发技巧——自定义控件之自定义属性
Android开发技巧——自定义控件之自定义属性
掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码。
上一篇讲了如何通过xml把几个控件组织起来,并继承某个ViewGroup子类,把它们封装起来使用。这是我们接触到的最简单的一种自定制控件了。但许多时候,我们还需要在布局文件中使用它们的时候,能通过属性传入一些值,来影响最终的显示结果。
我们在做项目中经常会遇到的一个情况:一张图片加一个文本的组合。比如充值账户成功之后显示的一个界面,上面是一个表示成功的图标,下面是对应的说明文字:“充值成功”。或者是在登录界面的账户输入框中,输入框的左边需要显示一个表示账户的图标。如下所示:
图标文字组合的笨重实现
一开始对于这样的情况,我们可能会采用ImageView
加TextView
的方式。后来通过lint工具的提示,或者是其他的方式,你可能会知道TextView的几个属性drawableLeft
,drawableRight
,drawableTop
以及drawableBottom
可以做到。但是使用的时候,你会发现这几个属性设置进去的图片,是按其本身大小来显示的。
好像也没关系,让设计师切好图就是了。但是心里却是没底的。因为Android手机万万种,你公司的测试机却只有那两三个,也许换上某个大屏低分辨率的千元机,图标就被撑大了。所以你还是希望能设定图片的大小。
使用一个TextView实现
设定一个TextView
的drawable大小,可以通过在java代码中调用drawable的setBounds(int left, int top, int right, int bottom)
方法设置它的界限,然后调用setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
方法把我们的drawable对象设置进去。
但是每次调用都要设置未免太麻烦,所以我们可以继承TextView
写一个DrawableTextView
,重写setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)
方法;或者是setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top, Drawable end, Drawable bottom)
,如果你是通过drawableStart
及drawableEnd
属性来设置图片的话。通过在这里对drawable进行setBounds()
,也一样能达到上面的效果。
现在我们用一个TextView实现了上面TextView+ImageView
的效果,达到了我们的目的:优化布局性能,简化布局代码。
但是对于这样的控件,我们可能在项目的多个地方用到,或者在其他项目也会用到。所以希望能把它做到更通用。这时我们就不能写死drawable的宽和高,而是希望能够在外部xml使用它时进行控制,也就是——自定义属性。
自定义属性
首先在res/values/attrs.xml
文件中(如果没有请创建)定义一个declare-styleable
,然后在里面定义两个属性,分别表示drawable的宽和高:
<declare-styleable name="DrawableTextView">
<attr name="drawableHeight" format="dimension"/>
<attr name="drawableWidth" format="dimension"/>
</declare-styleable>
其中declare-styleable
的名字name
通常写为我们的控件的类名,这样在写布局代码中,Android Studio就可以给我们对应的属性提示了。
在attr
中定义的是我们的属性,name
是属性的名字,format
是属性值的格式,这里我们定义为dimension
。
属性是定义好了,也可以布局代码中使用了。但是我们还需要在我们的自定义的DrawableTextView
中读取属性。首先重写构造方法:
public DrawableTextView(Context context) {
super(context);
}
public DrawableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
applyAttributes(context, attrs);
}
public DrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
applyAttributes(context, attrs);
}
然后在里面调用我们自定义的applyAttributes()
方法,在里面我们会读取到属性。我们需要在我们的类中声明两个属性:
private int mDrawableWidth;
private int mDrawableHeight;
然后在applyAttributes()
方法中,通过TypedArray获取属性值:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView);
mDrawableWidth = typedArray.getDimensionPixelSize(R.styleable.DrawableTextView_drawableWidth, 0);
mDrawableHeight = typedArray.getDimensionPixelSize(R.styleable.DrawableTextView_drawableHeight, 0);
typedArray.recycle();
这里说明一下,attrs是我们在xml中定义的相关联的属性集。通过调用context.obtainStyledAttributes(attrs, R.styleable.DrawableButton);
,我们能够接收到在Context所对应的theme中我们的样式属性,也就是这里的属性值我们也可以通过在theme中来设置。关于通过在theme中指定属性值,后续的博客会再详细说明,这里暂略不谈。第二个参数传的是我们所定义的declare-styleable
得到TypedArray
对象之后,通过它里面的getXXX方法我们可以得到各个属性的值,比如通过getDimensionPixelSize(int index, int defValue)
方法得到属性尺寸对应的像素大小。第一个参数是属性的索引,第二个是默认值。在获取完属性之后,必须调用typedArray.recycle()
对它进行回收,以便后面的调用者可以复用。因为Resources对象中会缓存一些TypedArray的数据,TypedArray使用的时候会从中获取,以优化内存利用。
拿到属性值并赋给我们的成员变量之后,我们就可以在后面的方法进行设置了。由于本篇主要讲自定义属性,关于该项目的后续实现,可以参考我在Github上的项目 DrawableWidget。
上面的例子中,关于在attrs
定义属性其实是存在着问题的。因为我们直接就定义了drawableWidth
及drawableHeight
属性,如果我有一个项目使用到它,并且同时使用了另外一个库,那个库也定义了这两个属性的话,就会产生冲突了。所以要强调一点:对于自定义属性,必须加上我们自己的前缀。比如把属性定义为nbDrawableWidth
等等。(我那个项目在写的时候还不成熟,而且自己有些懒,没遇到问题总是不想改,暂且留作反例吧。)
共用属性
还有一种情况。我们在布局里会经常遇到这样一种情况:
在布局里面的每一个条目,会用分割线分开。而这些条件还会分成几组,组里面的分割线是有左边距的。这样的情况很好实现,重写这个layout,在onDraw里面画线就可以了。我们把右边的每一个item看成是:上面一条满的分割线或者是不带分割线,下面是一条带边距的分割线,或者是满的分割线。这样我们就可以定义一个pwBorder属性,里面定义<flag name="TOP" value="1"/>``<flag name="BOTTOM" value="2"/>
表示标记位。然后在代码里获取pwBorder的值,与1做&操作,大于0就表示要画上面的分割线,否则不画,再和2做&操作,大于0就表示画完整的分割线,否则画带边距的分割线。如果需要左分割线或右分割线时,还可以再定义值为4或者8的标志位。
在xml引用它时,属性是这样的:
app:pwBorder="TOP|BOTTOM"
但是,可能除了这个RelativeLayout需要这种属性之外,我们在写的其他控件,可能也要包括这个属性以及其他属性。这时我们可以把attr
的定义抽出来,写在declare-styleable
节点外面。如下:
<!--带边框的Layout属性-->
<attr name="pwBorder">
<flag name="TOP" value="1"/>
<flag name="BOTTOM" value="2"/>
</attr>
<attr name="pwBorderWidth" format="dimension"/>
<attr name="pwBorderColor" format="color"/>
然后再这样使用:
<declare-styleable name="BorderLinearLayout">
<attr name="pwBorder"/>
<attr name="pwBorderWidth"/>
<attr name="pwBorderColor"/>
</declare-styleable>
<declare-styleable name="TextFieldView">
<attr name="pwBorder"/>
<attr name="pwBorderWidth"/>
<attr name="pwBorderColor"/>
<attr name="pwLabel"/>
<attr name="pwValue" format="string"/>
<attr name="pwIcon" format="reference"/>
</declare-styleable>
我建议使用这种方式来定义属性。因为这样当我们的属性与其他库的属性正好重名时,如果定义的format相同,也是可以被合并,并且通过编译的。
本文原创,转载请注明出处:http://blog.csdn.net/maosidiaoxian/article/details/50009725
Android开发技巧——自定义控件之自定义属性的更多相关文章
- Android开发技巧——自定义控件之使用style
Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...
- Android开发技巧——自定义控件之增加状态
Android开发技巧--自定义控件之增加状态 题外话 这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天.不想把这篇拖到下个月,所以还是先硬着头皮写了. ...
- Android开发技巧——自定义控件之组合控件
Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...
- Android开发技巧——写一个StepView
在我们的应用开发中,有些业务流程会涉及到多个步骤,或者是多个状态的转化,因此,会需要有相关的设计来展示该业务流程.比如<停车王>应用里的添加车牌的步骤. 通常,我们会把这类控件称为&quo ...
- Android开发技巧——实现可复用的ActionSheet菜单
在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...
- Android开发技巧——高亮的用户操作指南
Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...
- 50个android开发技巧
50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html
- Android开发技巧——大图裁剪
本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
随机推荐
- 有n个数,输出其中所有和为s的k个数的组合。
分析:此题有两个坑,一是这里的n个数是任意给定的,不一定是:1,2,3...n,所以可能有重复的数(如果有重复的数怎么处理?):二是不要求你输出所有和为s的全部组合,而只要求输出和为s的k个数的组合. ...
- java设计模式-----单例设计模式
设计模式是个很高深的东西,我也是略懂皮毛,下面让我用最简洁易懂的语言描述下单例设计模式吧. 一些人总结出来用来解决特定问题的固定的解决方案. 解决一个类在内存中只存在一个对象,想要保证对象的唯一. 1 ...
- JAXB(Java Architecture for XML Binding)
marshal(Java对象转化成XML) import javax.xml.bind.annotation.XmlRootElement; //指定根元素,其他属性默认为根元素的子元素 @XmlRo ...
- 剑指offer面试题6 重建二叉树(java)
注:(1)java中树的构建 (2)构建子树时可以直接利用Arrays.copyOfRange(preorder, from, to),这个方法是左开右闭的 package com.xsf.SordF ...
- App引导界面,可以这么玩
什么是ViewPager,刚一听到这个词,我们可能感觉很奇怪,但是我相信我们大部分人都曾见到过这些界面的.其实它就是我们在安装好一个app之后第一次使用时的那些引导界面的效果.这就是通过ViewPag ...
- Cocos2D iOS之旅:如何写一个敲地鼠游戏(九):创建动画
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- linux内核中默认logo的具体位置
/driver/logo/... 以下这个目录下对应的是logo的设置
- 1052. Linked List Sorting (25)
题目如下: A linked list consists of a series of structures, which are not necessarily adjacent in memory ...
- 【翻译】在Sencha应用程序中使用插件和混入
原文:Using Plugins and Mixins in Your Sencha Apps 概述 当扩展一个框架类的功能的时候,通常都会直接将新功能写入派生类,然而,如果所需的同一功能存在于多个组 ...
- 【一天一道LeetCode】#49. Group Anagrams
一天一道LeetCode系列 (一)题目 Given an array of strings, group anagrams together. For example, given: [" ...