Android 初階自定義 View 字符頭像

自己很少做自定義 View 惦积,只有最開始的時候跟著郭神寫了一個小 Demo ,后來隨著見識的越來越多猛频,特別是在開源社區(qū)看到很多優(yōu)秀的漂亮的控件狮崩,都是羨慕的要死蛛勉,但是拉下來的代碼還是看不明白,而且當(dāng)時因為時間因素睦柴,沒有深入學(xué)習(xí)和研究控件和動畫方面的知識诽凌,而是把更多時間花在了 Android 的異步通信和網(wǎng)絡(luò)框架這一塊。
因為想起暑假實習(xí)的時候有個小需求坦敌,當(dāng)時因為忙著主要的業(yè)務(wù)侣诵,一直擱淺沒有做,回到學(xué)校發(fā)現(xiàn)其實不難狱窘。索性從這個人生第一個上架的小控件慢慢深入一點杜顺,順帶復(fù)習(xí) View 的繪制原理。

目錄

目標(biāo)效果

需求:實習(xí)公司一個產(chǎn)品训柴,因為很多是臨時用戶哑舒,需要為這些沒有自覺設(shè)置頭像的用戶,給予隨機(jī)頭像幻馁。生成的規(guī)則是根據(jù)用戶用戶名的第一個字符隨機(jī)匹配顏色集洗鸵。

從需求中我們可以知道:

  • 該控件需要展示圖片
  • 該控件需要按照規(guī)則生成圖像
  • 一般頭像都是圓形

大致上可以知道是這樣的。
開搞仗嗦!

繼承 ImageView 開始

我們都知道 Android 自帶了很多控件膘滨,我們自定義控件的出發(fā)點只是官方提供的控件無法滿足業(yè)務(wù)需求的時候。
從我們的需求來看稀拐,該控件是圖片展示類的火邓,所以我們很自然想到了只需要在系統(tǒng) ImageView 上進(jìn)行功能拓展即可,這樣就可以滿足新的需求又不會失去 ImageView 自帶的功能德撬。

public class CharAvatarView extends ImageView {
    private static final String TAG = CharAvatarView.class.getSimpleName();
    // 顏色畫板集
    private static final int[] colors = {
        0xff1abc9c, 0xff16a085, 0xfff1c40f, 0xfff39c12, 0xff2ecc71,
        0xff27ae60, 0xffe67e22, 0xffd35400, 0xff3498db, 0xff2980b9,
        0xffe74c3c, 0xffc0392b, 0xff9b59b6, 0xff8e44ad, 0xffbdc3c7,
        0xff34495e, 0xff2c3e50, 0xff95a5a6, 0xff7f8c8d, 0xffec87bf,
        0xffd870ad, 0xfff69785, 0xff9ba37e, 0xffb49255, 0xffb49255, 0xffa94136
    };

    private Paint mPaintBackground;
    private Paint mPaintText;
    private Rect mRect;

    private String text;

    private int charHash;

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

    public CharAvatarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRect = new Rect();
    }
}

在這里我做了一些初始化工作铲咨,并且在其中的一個構(gòu)造函數(shù)中實例化了 PaintRect

關(guān)于 View 的構(gòu)造函數(shù)的區(qū)別:

public CharAvatarView(Context context) {
    super(context);
}
public CharAvatarView(Context context, AttributeSet attrs) {
    super(context, attrs);
}
public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}
  • 第一種屬于程序內(nèi)實例化時采用蜓洪,之傳入 Context 即可
CharAvatarView avatarView = new CharAvatarView(this);

這樣我們的 View 就新建出來了纤勒,根據(jù)需求添加到布局即可。

  • 第二種用于 layout 文件實例化隆檀,會把 XML 內(nèi)的參數(shù)通過 AttributeSet 帶入到 View 內(nèi)摇天。

  • 第三個主題的 style 信息,也會從 XML 里帶入

為了自定義的 View 兼容 Java 和 Xml 兩種代碼的使用方式恐仑,一般推薦這樣寫構(gòu)造方法:

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

    public CharAvatarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRect = new Rect();
    }

工作流程

我們的 View 系統(tǒng)是如何將它繪制到屏幕上的呢泉坐?

View 的繪制流程是從 ViewRoot 的 performTraversals 方法開始,它經(jīng)過 measure 裳仆、 layout 和 draw 三個過程才能最終將一個 View 繪制出來腕让,其中 measure 用來測量 View 的寬和高,layout 用來確定 View 在父容器中的放置位置歧斟,而 draw 則負(fù)責(zé)將 View 繪制在屏幕上纯丸。針對 performTraversals 的大致流程如圖:

Measure 過程決定了 View 的寬/高司训, Measure 完成以后,可以通過 getMeasuredWidthgetMeasuredHeight 方法來獲取到 View 測量后的寬/高液南,在幾乎所有的情況下它都等同于 View 最終的寬/高,但是特殊情況除外勾徽。
Layout 過程 決定了 View 的四個頂點的坐標(biāo)和實際的 View 的寬/高滑凉,完成以后,可以通過 getTop喘帚、getBottom畅姊、getLeftgetRight 來拿到 View 的四個頂點的位置吹由,并可以通過 getWidthgetHeight 方法拿到 View 最終的寬/高若未。
Draw 過程則決定了 View 的顯示,只有 draw 方法完成以后 View 的內(nèi)容才能呈現(xiàn)在屏幕上倾鲫。

關(guān)于 View 工作流程的深入我們在以后另外開篇進(jìn)行研究粗合。目前我們已經(jīng)從宏觀了解到了 View 會經(jīng)歷三個過程繪制出來,而且清楚了其中不同方法中的用途乌昔。接下來我們看看 CharAvatarView 在這三個流程中分別做了什么隙疚。

onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec); // 寬高相同
}

讓寬高相同,我在這里是只直接傳入寬度進(jìn)行測量磕道。
這樣會得到一個正方形的 View供屉。

onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}

我在這里什么也沒有做,因為需求里對 View 的位置沒有什么需要特殊的處理溺蕉。

onDraw()

大部分自定義控件伶丐,最核心的代碼就是在 onDraw() 里了。

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null != text) {
            int color = colors[charHash % colors.length];
            // 畫圓
            mPaintBackground.setColor(color);
            canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, mPaintBackground);
            // 寫字
            mPaintText.setColor(Color.WHITE);
            mPaintText.setTextSize(getWidth() / 2);
            mPaintText.setStrokeWidth(3);
            mPaintText.getTextBounds(text, 0, 1, mRect);
            // 垂直居中
            Paint.FontMetricsInt fontMetrics = mPaintText.getFontMetricsInt();
            int baseline = (getMeasuredHeight() - fontMetrics.bottom - fontMetrics.top) / 2;
            // 左右居中
            mPaintText.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(text, getWidth() / 2, baseline, mPaintText);
        }
    }
  1. 首先從顏色數(shù)組里根據(jù) hash 取余得到背景顏色
  2. 然后畫出背景圓
  3. 接下來就是寫字
  4. 最后是對字居中的處理
    /**
     * @param content 傳入字符內(nèi)容
     * 只會取內(nèi)容的第一個字符,如果是字母轉(zhuǎn)換成大寫
     */
    public void setText(String content) {
        if (content == null) {
            throw new NullPointerException("字符串內(nèi)容不能為空");
        }
        this.text = String.valueOf(content.toCharArray()[0]);
        this.text = text.toUpperCase();
        charHash = this.text.hashCode();
        // 重繪
        invalidate();
    }

這是暴露給外部的方法疯特,我們也是在這里得到要畫的字符哗魂。

使用

在 gradle 依賴?yán)锾砑?

compile 'com.github.xcc3641:charavatarview:0.1'
<com.hugo.charavatarview.CharAvatarView
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:id="@+id/avatar"/>
CharAvatarView mAvatarView;
mAvatarView = (CharAvatarView) findViewById(R.id.avatar);
mAvatarView.setText("謝三弟");

運行:


人生第一個自定義 View 就完成了。

上傳到可以參考司機(jī)的這篇文章碼農(nóng)必知之上傳開源庫到 jcenter辙芍,配置好各種參數(shù)啡彬。以后更新版本就執(zhí)行一行代碼就行啦。

./gradlew install // 只需要第一次執(zhí)行
./gradlew bintrayUpload

開源地址:GitHub 地址

額外閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末故硅,一起剝皮案震驚了整個濱河市庶灿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吃衅,老刑警劉巖往踢,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異徘层,居然都是意外死亡峻呕,警方通過查閱死者的電腦和手機(jī)利职,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘦癌,“玉大人猪贪,你說我怎么就攤上這事⊙端剑” “怎么了热押?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斤寇。 經(jīng)常有香客問我桶癣,道長,這世上最難降的妖魔是什么娘锁? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任牙寞,我火速辦了婚禮,結(jié)果婚禮上莫秆,老公的妹妹穿的比我還像新娘间雀。我一直安慰自己,他們只是感情好镊屎,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布雷蹂。 她就那樣靜靜地躺著,像睡著了一般杯道。 火紅的嫁衣襯著肌膚如雪匪煌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天党巾,我揣著相機(jī)與錄音萎庭,去河邊找鬼。 笑死齿拂,一個胖子當(dāng)著我的面吹牛驳规,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播署海,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼吗购,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砸狞?” 一聲冷哼從身側(cè)響起捻勉,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刀森,沒想到半個月后踱启,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年埠偿,在試婚紗的時候發(fā)現(xiàn)自己被綠了透罢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡冠蒋,死狀恐怖羽圃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抖剿,我是刑警寧澤统屈,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站牙躺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏腕扶。R本人自食惡果不足惜孽拷,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望半抱。 院中可真熱鬧脓恕,春花似錦、人聲如沸窿侈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽史简。三九已至乃秀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圆兵,已是汗流浹背跺讯。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工胁塞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留担钮,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓最住,卻偏偏與公主長得像超凳,于是被迫代替她去往敵國和親愈污。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,520評論 25 707
  • View的繪制和事件處理是兩個重要的主題轮傍,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 119,176評論 100 845
  • Android控件架構(gòu)與自定義控件(一) (本文并非原創(chuàng)文章暂雹,整理摘抄方便自己查看,原文地址為Android控件架...
    b5e7a6386c84閱讀 966評論 0 6
  • 大家好创夜,我叫土豆擎析。 別笑,我知道這名字很土,我也很想抗議揍魂,誰知道幾百年前是哪個二把刀給我起了這個土里土氣的名字桨醋。名...
    秋水凌天閱讀 518評論 0 0
  • 如今自以為攀上了西歐 某國又施舍了你幾塊骨頭 你就不知道了天高地厚 甘愿當(dāng)他的馬前卒 當(dāng)他的哈巴狗 呲牙露齒夾著尾...
    王小永_6be2閱讀 347評論 3 8