Android 初階自定義 View 字符頭像

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

目錄

目標效果

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

從需求中我們可以知道:

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

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

繼承 ImageView 開始

我們都知道 Android 自帶了很多控件,我們自定義控件的出發(fā)點只是官方提供的控件無法滿足業(yè)務(wù)需求的時候。
從我們的需求來看,該控件是圖片展示類的,所以我們很自然想到了只需要在系統(tǒng) ImageView 上進行功能拓展即可,這樣就可以滿足新的需求又不會失去 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 則負責將 View 繪制在屏幕上。針對 performTraversals 的大致流程如圖:

Measure 過程決定了 View 的寬/高, Measure 完成以后,可以通過 getMeasuredWidthgetMeasuredHeight 方法來獲取到 View 測量后的寬/高,在幾乎所有的情況下它都等同于 View 最終的寬/高,但是特殊情況除外。
Layout 過程 決定了 View 的四個頂點的坐標和實際的 View 的寬/高,完成以后,可以通過 getTop、getBottomgetLeft、getRight 來拿到 View 的四個頂點的位置,并可以通過 getWidthgetHeight 方法拿到 View 最終的寬/高。
Draw 過程則決定了 View 的顯示,只有 draw 方法完成以后 View 的內(nèi)容才能呈現(xiàn)在屏幕上。

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

onMeasure()

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

讓寬高相同,我在這里是只直接傳入寬度進行測量。
這樣會得到一個正方形的 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 依賴里添加:

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 就完成了。

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

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

開源地址:GitHub 地址

額外閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容