本文为新书《Android/OPhone开发完全讲义》 的内容连载。 《Android/OPhone开发完全讲义》 一 书一将在近期出版,敬请关注。
在本例中要实现一个可以在文本前方添加一个图像(可以是任何 Android 系统支持的图像格式)的 TextView 组件。在编写代码之前,先看一下 Android 组件的配置代码。
<
TextView
android:id
="@+id/textview1"
android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="textview1"
/>
上面的代码配置了一个标准的 TextView 组件。在这段代码中主要有两部分组成:组件标签( <TextView> )和标签属性( android:id 、 android:layout_width 等)。需要注意的是,在所有的标签属性前面都需要加了一个命名空间( android )。实际上, android 命名空间的值是在 Android 系统中预定义的,所有 Android 系统原有的组件在配置时都需要在标签属性前加 android 。 对于定制组件,可以有如下 3 种选择。 1. 仍然沿用android命名空间。 2. 改用其他的命名空间。 3. 不使用命名空间。
虽然上面 3 种选择从技术上说都没有问题,但作者建议使用第 2 种方式(尤其是对外发布的组件),这是因为在使用定制组件时,可能需要指定相同名称的属性,在这种情况下,可以通过命名空间来区分这些属性,例如,有两个命名空间: android 和 mobile ,这时可以在各自的命名空间下有相同名称的属性,如 android:src 和 mobile:src 。在本例中定义了一个 mobile 命名空间,因此,在配置本例实现的组件时需要在属性前加 mobile 。 实现定制组件的一个重要环节就是读取配置文件中相应标签的属性值,由于本例要实现的组件类需要从 TextView 类继承,因此,只需要覆盖 TextView 类中带 AttributeSet 类型参数的构造方法即可,该构造方法的定义如下:
public
TextView(Context context, AttributeSet attrs)
在构造方法中可以通过 AttributeSet 接口的相应 getter 方法来读取指定的属性值,如果在配置属性时指定了命名空间,需要在使用 getter 方法获得属性值时指定这个命名空间,如果未指定命名空间,则将命名空间设为 null 即可。
IconTextView 是本例要编写的组件类,该类从 TextView 继承,在 onDraw 方法中将 TextView 中的文本后移,并在文本的前方添加了一个图像,该图像的资源 ID 通过 mobile:iconSrc 属性来指定。 IconTextView 类的代码如下:
package
net.blogjava.mobile.widget;
import
android.content.Context;
import
android.graphics.Bitmap;
import
android.graphics.BitmapFactory;
import
android.graphics.Canvas;
import
android.graphics.Rect;
import
android.util.AttributeSet;
import
android.widget.TextView;
public
class
IconTextView
extends
TextView {
//
命名空间的值
private
final
String namespace
=
"
http://net.blogjava.mobile
"
;
//
保存图像资源ID的变量
private
int
resourceId
=
0
;
private
Bitmap bitmap;
public
IconTextView(Context context, AttributeSet attrs) {
super
(context, attrs);
//
getAttributeResourceValue方法用来获得组件属性的值,在本例中需要通过该方法的第1个参数指
//
定命名空间的值。该方法的第2个参数表示组件属性名(不包括命名空间名称),第3个参数表示默
//
认值,也就是如果该属性不存在,则返回第3个参数指定的值
resourceId
=
attrs.getAttributeResourceValue(namespace,
"
iconSrc
"
,
0
);
if
(resourceId
>
0
)
//
如果成功获得图像资源的ID,装载这个图像资源,并创建Bitmap对象
bitmap
=
BitmapFactory.decodeResource(getResources(), resourceId); } @Override
protected
void
onDraw(Canvas canvas) {
if
(bitmap
!=
null
) {
//
从原图上截取图像的区域,在本例中为整个图像
Rect src
=
new
Rect();
//
将截取的图像复制到bitmap上的目标区域,在本例中与复制区域相同
Rect target
=
new
Rect(); src.left
=
0
; src.top
=
0
; src.right
=
bitmap.getWidth(); src.bottom
=
bitmap.getHeight();
int
textHeight
=
(
int
) getTextSize(); target.left
=
0
;
//
计算图像复制到目标区域的纵坐标。由于TextView组件的文本内容并不是
//
从最顶端开始绘制的,因此,需要重新计算绘制图像的纵坐标
target.top
=
(
int
) ((getMeasuredHeight()
-
getTextSize())
/
2
)
+
1
; target.bottom
=
target.top
+
textHeight;
//
为了保证图像不变形,需要根据图像高度重新计算图像的宽度
target.right
=
(
int
) (textHeight
*
(bitmap.getWidth()
/
(
float
) bitmap.getHeight()));
//
开始绘制图像
canvas.drawBitmap(bitmap, src, target, getPaint());
//
将TextView中的文本向右移动一定的距离(在本例中移动了图像宽度加2个象素点的位置)
canvas.translate(target.right
+
2
,
0
); }
super
.onDraw(canvas); } }
在编写上面代码时需要注意如下3点: 1. 需要指定命名空间的值。该值将在<LinearLayout>标签的xmlns:mobile属性中定义。 2. 如果在配置组件的属性时指定了命名空间,需要在AttributeSet 接口的相应getter方法中的第1个参数指定命名空间的值,而第2个参数只需指定不带命名空间的属性名即可。 3. TextView类中的onDraw方法一定要在translate方法后面执行,否则系统不会移动TextView中的文本。
下面在 main.xml 文件中配置了 7 个 IconTextView 组件,分别设置了不同的字体大小,同时,文本前面的图像也会随着字体大小的变化而放大或缩小,配置代码如下:
<?
xml version="1.0" encoding="utf-8"
?>
<!--
在下面的标签中通过xmlns:mobile属性定义了一个命名空间
-->
<
LinearLayout
xmlns:android
="http://schemas.android.com/apk/res/android"
xmlns:mobile
="http://net.blogjava.mobile"
android:orientation
="vertical"
android:layout_width
="fill_parent"
android:layout_height
="fill_parent"
>
<!--
mobile:iconSrc是可选属性,如果未设置该属性,则IconTextView与TextView的效果相同
-->
<!--
由于IconTextView和Main类不在同一个包中,因此,需要显式指定package
-->
<
net
.blogjava.mobile.view.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第一个笑脸"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第二个笑脸"
android:textSize
="24dp"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第三个笑脸"
android:textSize
="36dp"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第四个笑脸"
android:textSize
="48dp"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第五个笑脸"
android:textSize
="36dp"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第六个笑脸"
android:textSize
="24dp"
mobile:iconSrc
="@drawable/small"
/>
<
net
.blogjava.mobile.widget.IconTextView android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="第七个笑脸"
mobile:iconSrc
="@drawable/small"
/>
</
LinearLayout
>
运行本实例后,将显示如图 1 所示的效果。
注意:
虽然很多人认为 组件的属性必须以 android 命名空间开头,该命名空间的值必须是 http://schemas.android.com/apk/res/android 。实际上,只是命名空间的值必须是 http://schemas.android.com/apk/res/android 而已,命名空间的名称可以是任何值,如下面的代码所示:
<?
xml version="1.0" encoding="utf-8"
?>
<!--
将android换成了abcd
-->
<
LinearLayout
xmlns:abcd
="http://schemas.android.com/apk/res/android"
abcd:orientation
="vertical"
abcd:layout_width
="fill_parent"
abcd:layout_height
="fill_parent"
>
</
LinearLayout
>
转自
http://www.blogjava.net/nokiaguy/archive/2010/04/29/319652.html