天天看點

新書内容連載(1):自定義Android元件之帶圖像的TextView本文為原創,如需轉載,請注明作者和出處,謝謝!

本文為新書《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:text="第二個笑臉" android:textSize="24dp" mobile:iconSrc="@drawable/small" />

        android:text="第三個笑臉" android:textSize="36dp" mobile:iconSrc="@drawable/small" />

        android:text="第四個笑臉" android:textSize="48dp" mobile:iconSrc="@drawable/small" />

        android:text="第五個笑臉" android:textSize="36dp" mobile:iconSrc="@drawable/small" />

        android:text="第六個笑臉" android:textSize="24dp" mobile:iconSrc="@drawable/small" />

        android:text="第七個笑臉" mobile:iconSrc="@drawable/small" />

</LinearLayout> 

運作本執行個體後,将顯示如圖1所示的效果。

新書内容連載(1):自定義Android元件之帶圖像的TextView本文為原創,如需轉載,請注明作者和出處,謝謝!

注意:雖然很多人認為元件的屬性必須以android命名空間開頭,該命名空間的值必須是http://schemas.android.com/apk/res/android。實際上,隻是命名空間的值必須是http://schemas.android.com/apk/res/android而已,命名空間的名稱可以是任何值,如下面的代碼所示:

<!--  将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">

新書内容連載(1):自定義Android元件之帶圖像的TextView本文為原創,如需轉載,請注明作者和出處,謝謝!
新書内容連載(1):自定義Android元件之帶圖像的TextView本文為原創,如需轉載,請注明作者和出處,謝謝!

</LinearLayout>

繼續閱讀