安卓開發(fā)筆記——自定義控件學(xué)習(xí)小結(jié)

在很多安卓崗位的職位描述上犁享,都會提到一個“自定義控件”。這個東西上手其實并不難豹休,但真想做好自定義控件炊昆,要會的東西還挺多。下面我就分享個簡單的例子威根,給自己凤巨,也給需要的人。

需求:服務(wù)端傳過來的數(shù)據(jù)長這個樣子:
這是一個<important>甲方爸爸</important>特別<important>強(qiáng)調(diào)</important>的需求
在TextView中要顯示成這個樣子:
這是一個\color{#ff8200}{甲方爸爸}特別\color{#ff8200}{強(qiáng)調(diào)}的需求

就這個需求

下面就是我的進(jìn)化之路:

零洛搀、不使用自定義控件

其實就是簡單的文字替換敢茁,Android中有一個Spannable

public static Spanned important(String text, String color) {
   if ( text.split("<important>").length < 2) return new SpannableString(important);
   text = text.replace("/ important", "/font></html") .replace(" important", "html><font color=\"" + color + "\"");
   return Html.fromHtml(text);
}

純屬一個方法,于是在那個頁面里姥卢,滿眼望去全是important:

tvName.setText(important(object.getString("name")));
tvJob.setText(important(object.getString("job"))); 
tvDevice.setText(important(object.getString("device")));

然后就引出了第一種自定義控件

一卷要、擴(kuò)展控件

最簡單的渣聚,是基于現(xiàn)有控件進(jìn)行控件擴(kuò)展。既然方法已經(jīng)寫好了僧叉,繼承原來的TextView寫一個新的控件奕枝,把setText()方法重寫一下就好啦:

public class ImportantTextView extends AppCompatTextView {
    public ImportantTextView(Context context) {
        super(context);
    }
    public ImportantTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    public ImportantTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    public void setText(String text) {
        setImportantText(text, "#ff8200");
    }
    public void setImportantText(String text, String color) {
        if (text.split("<mportant>").length < 2) {
            setText(text);
        } else {
            text = text.replace("/mportant", "/font></html").replace("em", "html><font color=\"" + color + "\"");
            setText(Html.fromHtml(text));
        }
     }
}

補(bǔ)充:為什么繼承AppCompatTextView而不是TextView?
AppCompatTextView是在API level 23引入的瓶堕,繼承自TextView隘道,它是Android標(biāo)準(zhǔn)TextView的增強(qiáng),特點就是可以自適應(yīng)字體寬度大小變化郎笆。有一點要提的是谭梗,在android官方文檔中,xml里寫的傳統(tǒng)TextView已經(jīng)被編譯器替換成AppCompatTextView了宛蚓,不需要開發(fā)者再去手動替換激捏。
另外,如果自定義控件上面繼承TextView凄吏,會報錯的远舅。。痕钢。

但是图柏,就這么寫一個控件實在是拿不出手,其實就相當(dāng)于把外面的方法放到了控件里任连,太沒技術(shù)含量了

二蚤吹、重新繪制控件

先重寫onLayout():

略,因為沒寫~

再重寫onMeasure():

略随抠,因為也沒寫~

上面這兩個方法裁着,前者用來確定控件在父控件的位置,后者用來測量控件的寬高大小暮刃。
在這里我只用到了onDraw()方法:

@Override
protected void onDraw(Canvas canvas) {
    if (text == null || text.isEmpty())
        return; // 如果沒有輸入文字跨算,也就沒有意義畫出控件了
    if (text.split("<important>").length < 2)
        canvas.drawText(text, 0, getHeight(), mPaint); // 如果沒有重點標(biāo)簽或者標(biāo)簽只出了一個,也沒有必要進(jìn)行加工了
    else {
        String[] t = text.replace("/important", "")
                        .replace("important", "")
                        .split("<>");
        float x = 0.0f;
        for (int i = 0; i < t.length; i++) {
        int textWidth = 0;
        canvas.save();
        if (i % 2 == 0) {
            canvas.drawText(t[i], x, getY(), mPaint);
            textWidth = getTextWidth(mPaint, t[i]);
        } else {
            canvas.drawText(t[i], x, getY(), mEMPaint);
            textWidth = getTextWidth(mEMPaint, t[i]);
        }
        x = x + textWidth;
        canvas.restore();
        }
     }
    requestLayout();
}

在繪制的時候就直接把顏色涂到上面椭懊,這樣比之前的更顯技術(shù)含量~

沒完带膀,其實還有第三種

三确憨、基于控件容器的自定義控件

最簡單驹碍,效率一般柳琢,可讀性一般,功能性簡單盅抚,非通用方法

在這個需求要怎么寫呢漠魏?
先要有一個布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/text9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

而控件代碼如下:

public ImportantTextView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    LayoutInflater.from(context).inflate(R.layout.layout_important, this, true); 
    tvTexts = new ArrayList<>();
    tvTexts.add((TextView) findViewById(R.id.text1));
    tvTexts.add((TextView) findViewById(R.id.text2));
    tvTexts.add((TextView) findViewById(R.id.text3));
    tvTexts.add((TextView) findViewById(R.id.text4));
    tvTexts.add((TextView) findViewById(R.id.text5));
    tvTexts.add((TextView) findViewById(R.id.text6));
    tvTexts.add((TextView) findViewById(R.id.text7));
}

public void setText(String text) {
    if (text == null || text.isEmpty())
        return;
    if (text.split("<important>").length < 2) {
        tvTexts.get(0).setText(text);
    } else {
        String[] t = text.replace("/important", "").replace("important", "").split("<>");
        int i = 0;
        while (i < t.length && i < tvTexts.size() - 1) {
            if (i % 2 != 0)
            tvTexts.get(i).setTextColor(0xffff8200);
            tvTexts.get(i).setText(t[i]);
            i++;
        }
        if (i >= tvTexts.size() - 1) {
            StringBuilder last = new StringBuilder();
            for (; i < t.length; i++) {
                last.append(t[i]);
            }
            tvTexts.get(tvTexts.size() - 1).setText(last);
        }
    }
}

簡單粗暴,就是獲得控件妄均,給控件賦值柱锹。這種自定義控件可讀性極強(qiáng)哪自,但復(fù)用性極差,其實就是把很多原生空間整合到了一個控件容器禁熏,然后在項目中以整體形式出現(xiàn)壤巷。

這個控件不適用于這個需求,但并不代表這個方法沒有用瞧毙,通常這種做法用在列表控件或者重復(fù)性的布局上胧华。


補(bǔ)充說明:

如果:這段<important>文字</important>是<important>這個<important>樣子的
我這里顯示:這段\color{#ff8200}{文字}\color{#ff8200}{這個}樣子的
但按照需求應(yīng)該:這段\color{#ff8200}{文字}是<important>這個<important>樣子的

這個是不太符合需求的,不過這個是線上項目宙彪,服務(wù)端的大哥指著房頂中央空調(diào)跟我保證過不會有這種情況矩动,所以暫且忽略掉。
而且我也沒想好如果真是這樣該怎么處理释漆,希望看到的能回貼幫我想一下悲没,謝謝~


下面貼出第二套方案的完整代碼:

package com.myprj.important.ImportantTextView;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;

import com.myprj.important.R;

public class ImportantTextView2 extends AppCompatTextView {

    private Paint mPaint, mImportantPaint;
    private String text;

    public ImportantTextView2(Context context) {
        this(context, null);
    }

    public ImportantTextView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ImportantTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context, attrs);
    }

    private void initPaint(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.importantTextView2);
        int color = array.getColor(R.styleable.ImportantTextView2_color, getTextColors().getDefaultColor());
        int importantColor = array.getColor(R.styleable.ImportantTextView2_importantColor, 0xffff8200);
        mPaint = getPaintByColor(color);
        mImportantPaint = getPaintByColor(importantColor);
        array.recycle();
    }

    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setTextSize(getTextSize());
        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (text == null || text.isEmpty())
            return;
        if (text.split("<important>").length < 2)
            canvas.drawText(text, 0, getHeight(), mPaint);
        else {
            String[] t = text.replace("/important", "").replace("important", "").split("<>");
            float x = 0.0f;
            for (int i = 0; i < t.length; i++) {
                int textWidth = 0;
                canvas.save();
                if (i % 2 == 0) {
                    canvas.drawText(t[i], x, getY(), mPaint);
                    textWidth = getTextWidth(mPaint, t[i]);
                } else {
                    canvas.drawText(t[i], x, getY(), mImportantPaint);
                    textWidth = getTextWidth(mImportantPaint, t[i]);
                }
                x = x + textWidth;
                canvas.restore();
            }
        }
        requestLayout();
    }

    public static int getTextWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet;
    }

    public void setText(String text) {
        this.text = text;
        super.setText(text);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市男图,隨后出現(xiàn)的幾起案子檀训,更是在濱河造成了極大的恐慌,老刑警劉巖享言,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渗鬼,居然都是意外死亡览露,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門譬胎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來差牛,“玉大人,你說我怎么就攤上這事堰乔∑” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵镐侯,是天一觀的道長侦讨。 經(jīng)常有香客問我,道長苟翻,這世上最難降的妖魔是什么韵卤? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮崇猫,結(jié)果婚禮上沈条,老公的妹妹穿的比我還像新娘。我一直安慰自己诅炉,他們只是感情好蜡歹,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布屋厘。 她就那樣靜靜地躺著,像睡著了一般月而。 火紅的嫁衣襯著肌膚如雪汗洒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天景鼠,我揣著相機(jī)與錄音仲翎,去河邊找鬼。 笑死铛漓,一個胖子當(dāng)著我的面吹牛溯香,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浓恶,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼玫坛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了包晰?” 一聲冷哼從身側(cè)響起湿镀,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伐憾,沒想到半個月后勉痴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡树肃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年蒸矛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胸嘴。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡雏掠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劣像,到底是詐尸還是另有隱情乡话,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布耳奕,位于F島的核電站绑青,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屋群。R本人自食惡果不足惜时迫,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谓晌。 院中可真熱鬧掠拳,春花似錦、人聲如沸纸肉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姐刁,卻和暖如春芥牌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聂使。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工壁拉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柏靶。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓弃理,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屎蜓。 傳聞我的和親對象是個殘疾皇子痘昌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359