本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!
本文為新書(shū)《Android/OPhone
開(kāi)發(fā)完全講義》的內(nèi)容連載。《Android/OPhone開(kāi)發(fā)完全講義》一書(shū)現(xiàn)已出版,敬請(qǐng)關(guān)注。
購(gòu) 買(mǎi):
互動(dòng)網(wǎng)
《Android/OPhone
開(kāi)發(fā)完全講義》目錄
源代碼下載
在本例中要實(shí)現(xiàn)一個(gè)可以在文本前方添加一個(gè)圖像(可以是任何Android系統(tǒng)支持的圖像格式)的TextView組件。在編寫(xiě)代碼之前,先看一下Android組件的配置代碼。
<TextView android:id="@+id/textview1" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="textview1" />
上面的代碼配置了一個(gè)標(biāo)準(zhǔn)的TextView組件。在這段代碼中主要有兩部分組成:組件標(biāo)簽(<TextView>)和標(biāo)簽屬性(android:id、android:layout_width等)。需要注意的是,在所有的標(biāo)簽屬性前面都需要加了一個(gè)命名空間(android)。實(shí)際上,android命名空間的值是在Android系統(tǒng)中預(yù)定義的,所有Android系統(tǒng)原有的組件在配置時(shí)都需要在標(biāo)簽屬性前加android。
對(duì)于定制組件,可以有如下3種選擇。
1. 仍然沿用android命名空間。
2. 改用其他的命名空間。
3. 不使用命名空間。
雖然上面3種選擇從技術(shù)上說(shuō)都沒(méi)有問(wèn)題,但作者建議使用第2種方式(尤其是對(duì)外發(fā)布的組件),這是因?yàn)樵谑褂枚ㄖ平M件時(shí),可能需要指定相同名稱(chēng)的屬性,在這種情況下,可以通過(guò)命名空間來(lái)區(qū)分這些屬性,例如,有兩個(gè)命名空間:android和mobile,這時(shí)可以在各自的命名空間下有相同名稱(chēng)的屬性,如android:src和mobile:src。在本例中定義了一個(gè)mobile命名空間,因此,在配置本例實(shí)現(xiàn)的組件時(shí)需要在屬性前加mobile。
實(shí)現(xiàn)定制組件的一個(gè)重要環(huán)節(jié)就是讀取配置文件中相應(yīng)標(biāo)簽的屬性值,由于本例要實(shí)現(xiàn)的組件類(lèi)需要從TextView類(lèi)繼承,因此,只需要覆蓋TextView類(lèi)中帶AttributeSet類(lèi)型參數(shù)的構(gòu)造方法即可,該構(gòu)造方法的定義如下:
public TextView(Context context, AttributeSet attrs)
在構(gòu)造方法中可以通過(guò)AttributeSet接口的相應(yīng)getter方法來(lái)讀取指定的屬性值,如果在配置屬性時(shí)指定了命名空間,需要在使用getter方法獲得屬性值時(shí)指定這個(gè)命名空間,如果未指定命名空間,則將命名空間設(shè)為null即可。
IconTextView是本例要編寫(xiě)的組件類(lèi),該類(lèi)從TextView繼承,在onDraw方法中將TextView中的文本后移,并在文本的前方添加了一個(gè)圖像,該圖像的資源ID通過(guò)mobile:iconSrc屬性來(lái)指定。IconTextView類(lèi)的代碼如下:
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方法用來(lái)獲得組件屬性的值,在本例中需要通過(guò)該方法的第1個(gè)參數(shù)指
// 定命名空間的值。該方法的第2個(gè)參數(shù)表示組件屬性名(不包括命名空間名稱(chēng)),第3個(gè)參數(shù)表示默
// 認(rèn)值,也就是如果該屬性不存在,則返回第3個(gè)參數(shù)指定的值
resourceId = attrs.getAttributeResourceValue(namespace, "iconSrc", 0);
if (resourceId > 0)
// 如果成功獲得圖像資源的ID,裝載這個(gè)圖像資源,并創(chuàng)建Bitmap對(duì)象
bitmap = BitmapFactory.decodeResource(getResources(), resourceId);
}
@Override
protected void onDraw(Canvas canvas)
{
if (bitmap != null)
{
// 從原圖上截取圖像的區(qū)域,在本例中為整個(gè)圖像
Rect src = new Rect();
// 將截取的圖像復(fù)制到bitmap上的目標(biāo)區(qū)域,在本例中與復(fù)制區(qū)域相同
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;
// 計(jì)算圖像復(fù)制到目標(biāo)區(qū)域的縱坐標(biāo)。由于TextView組件的文本內(nèi)容并不是
// 從最頂端開(kāi)始繪制的,因此,需要重新計(jì)算繪制圖像的縱坐標(biāo)
target.top = (int) ((getMeasuredHeight() - getTextSize()) / 2) + 1;
target.bottom = target.top + textHeight;
// 為了保證圖像不變形,需要根據(jù)圖像高度重新計(jì)算圖像的寬度
target.right = (int) (textHeight * (bitmap.getWidth() / (float) bitmap.getHeight()));
// 開(kāi)始繪制圖像
canvas.drawBitmap(bitmap, src, target, getPaint());
// 將TextView中的文本向右移動(dòng)一定的距離(在本例中移動(dòng)了圖像寬度加2個(gè)象素點(diǎn)的位置)
canvas.translate(target.right + 2, 0);
}
super.onDraw(canvas);
}
}
在編寫(xiě)上面代碼時(shí)需要注意如下3點(diǎn):
1. 需要指定命名空間的值。該值將在<LinearLayout>標(biāo)簽的xmlns:mobile屬性中定義。
2. 如果在配置組件的屬性時(shí)指定了命名空間,需要在AttributeSet 接口的相應(yīng)getter方法中的第1個(gè)參數(shù)指定命名空間的值,而第2個(gè)參數(shù)只需指定不帶命名空間的屬性名即可。
3. TextView類(lèi)中的onDraw方法一定要在translate方法后面執(zhí)行,否則系統(tǒng)不會(huì)移動(dòng)TextView中的文本。
下面在main.xml文件中配置了7個(gè)IconTextView組件,分別設(shè)置了不同的字體大小,同時(shí),文本前面的圖像也會(huì)隨著字體大小的變化而放大或縮小,配置代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- 在下面的標(biāo)簽中通過(guò)xmlns:mobile屬性定義了一個(gè)命名空間 -->
<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是可選屬性,如果未設(shè)置該屬性,則IconTextView與TextView的效果相同 -->
<!-- 由于IconTextView和Main類(lèi)不在同一個(gè)包中,因此,需要顯式指定package -->
<net.blogjava.mobile.view.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第一個(gè)笑臉" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第二個(gè)笑臉" android:textSize="24dp" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第三個(gè)笑臉" android:textSize="36dp" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第四個(gè)笑臉" android:textSize="48dp" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第五個(gè)笑臉" android:textSize="36dp" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第六個(gè)笑臉" android:textSize="24dp" mobile:iconSrc="@drawable/small" />
<net.blogjava.mobile.widget.IconTextView
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="第七個(gè)笑臉" mobile:iconSrc="@drawable/small" />
</LinearLayout>
運(yùn)行本實(shí)例后,將顯示如圖1所示的效果。

注意:雖然很多人認(rèn)為組件的屬性必須以android命名空間開(kāi)頭,該命名空間的值必須是http://schemas.android.com/apk/res/android。實(shí)際上,只是命名空間的值必須是http://schemas.android.com/apk/res/android而已,命名空間的名稱(chēng)可以是任何值,如下面的代碼所示:
<?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://t.sina.com.cn/androidguy 昵稱(chēng):李寧_Lining