VirtualView Android 實(shí)現(xiàn)詳解(三)—— 添加一個(gè)自定義控件

本系列文章

前文介紹了模板的基本格式、虛擬控件與原生控件混合使用的方式亏较。本文重點(diǎn)在把這兩塊內(nèi)容串起來(lái)介紹一下遵岩,如何實(shí)現(xiàn)從模板生成一個(gè)運(yùn)行時(shí)的控件尘执,并如何注冊(cè)一個(gè)自定義控件使用宴凉。

相關(guān)開(kāi)源庫(kù)

Android

iOS

名詞解釋

從 XML 到運(yùn)行時(shí)實(shí)例

舉個(gè)簡(jiǎn)單的例子挪凑,在 XML 模板里躏碳,可能會(huì)有這么一塊控件的使用:

<NText
    id="1"
    text="title"
    textSize="12"
    textColor="#333333"
    layoutWidth="wrap_content"
    layoutHeight="wrap_content"
    lineSpaceMultiplier="1.1"
    lines="2"
    flag="flag_event|flag_exposure|flag_clickable"
 />

這在 VirtualView 表示引用一個(gè)文本控件(VirtualView 內(nèi)置支持的所有控件見(jiàn)文檔)散怖,在《VirtualView Android實(shí)現(xiàn)詳解(一)—— 文件格式與模板編譯》里曾講過(guò)會(huì)將 XML 里的字符串等編譯成整型數(shù)值或者索引來(lái)降低解析成本镇眷。因此從在 XML 里使用一個(gè)控件到運(yùn)行時(shí)渲染它欠动,就要經(jīng)過(guò)一系列的轉(zhuǎn)換過(guò)程,其中有一半的過(guò)程是事先離線執(zhí)行的翅雏,另一半的過(guò)程才是在客戶端里運(yùn)行時(shí)執(zhí)行望几。以下這張圖概括了整個(gè)流程:

image

說(shuō)明一下每個(gè)步驟:

  • 先編寫(xiě) XML 文件橄抹,如圖所示引用了一個(gè) NText 控件害碾;
  • 在 Config 文件里配置 NText 的標(biāo)簽名及屬性的映射關(guān)系;像這種內(nèi)置控件都已經(jīng)配置好了芬沉,如果是自定義屬性和控件,才要操作自己添加配置剃袍;配置文件含義參考這篇文章:VirtualView 工具大更新啦民效。在這個(gè)示例中涛救,NText 標(biāo)簽被編譯工具編譯成數(shù)字 7检吆,屬性名 id蹭沛、text、textSize咆贬、textColor 都被編譯成一個(gè) hashcode 索引掏缎,真正的字符串值會(huì)存儲(chǔ)到字符串資源區(qū)御毅;屬性值 title 也是被編譯成一個(gè) hashcode 索引端蛆,真正的字符串值會(huì)存儲(chǔ)到字符串資源區(qū)今豆;屬性值 12 被直接編譯成數(shù)字 12呆躲; 屬性值 #333333 被編譯成顏色值 -13421773捶索;
  • 編譯工具根據(jù) XML 文件和 Config 文件編譯出一份二進(jìn)制文件,交給客戶端使用酝润;
  • 客戶端初始化框架的時(shí)候會(huì)根據(jù) id 注冊(cè)控件要销,在這個(gè)示例中 7 代表了 NativeText 類控件疏咐,它就用來(lái)實(shí)例化 XML 里的 NText 標(biāo)簽脐供;
  • 最后將 XML 里 NText 下的屬性傳給 NativeText 實(shí)例進(jìn)一步用于渲染;

創(chuàng)建控件實(shí)例的過(guò)程

以創(chuàng)建一個(gè) PicassoImage 為例(雖然內(nèi)置了 VImage 和 NImage 兩個(gè)控件缩举,但在實(shí)際業(yè)務(wù)場(chǎng)景中仅孩,還是使用一個(gè)自定義的圖片控件比較合適辽慕,這樣可以更好利用起結(jié)合圖片庫(kù)的內(nèi)存管理溅蛉、性能優(yōu)化等 feature)船侧。

目標(biāo)

  • 實(shí)現(xiàn)一個(gè)原生 Image 控件镜撩,使用 Picasso 加載圖片
  • 支持綁定 url 屬性用來(lái)加載網(wǎng)絡(luò)圖片
  • 支持綁定 degree 屬性用來(lái)旋轉(zhuǎn)圖片

1. 定義標(biāo)簽名及其 id袁梗,屬性名及類型

在編譯工具里配置文件里定義:

  • VIEW_ID_PicassoImage=1014遮怜,其中 PicassoImage 就是 XML 里的標(biāo)簽名锯梁,id 值為 1014,這個(gè)是自定義的拜姿,建議從 1001開(kāi)始,前 1000 保留給系統(tǒng)使用蛤肌;
  • degree=Float裸准,表示屬性名是 degree 炒俱,屬性值按 Float 類型編譯解析权悟;
  • url=String峦阁,表示屬性名是 url榔昔,屬性值按 String 類型編譯瘪菌,不過(guò)未在配置文件里聲明的屬性都是按 String 類型編譯的诵肛,所以可以省略疆栏;

2. 定義控件的載體 View

取名 PicassoImageView珠洗,繼承 ImageView许蓖,實(shí)現(xiàn) IView 接口膊爪,因?yàn)?demo 比較簡(jiǎn)單沛豌,除此之外不做其他邏輯赃额,主要實(shí)現(xiàn) IView 的接口調(diào)用對(duì)應(yīng)的系統(tǒng) measure加派、layout 方法,因?yàn)檫@些方法是不能在外部調(diào)用的跳芳,只能通過(guò) IView 的接口封裝一下暴露出去芍锦。

詳細(xì)代碼:PicassoImageView.java

3. 定義控件 model

取名 PicassoImage,繼承 ViewBase飞盆,在構(gòu)造函數(shù)里實(shí)例化 PicassoImageView娄琉,并獲取自定義屬性的 id;

public PicassoImage(VafContext context,
        ViewCache viewCache) {
        super(context, viewCache);
        mPicassoImageView = new PicassoImageView(context.getContext());
        StringSupport mStringSupport = context.getStringLoader();
        // 這里會(huì)取加載的模板數(shù)據(jù)里取獲取對(duì)應(yīng)的 id吓歇,第一個(gè)參數(shù)是屬性名孽水,第二個(gè)參數(shù)應(yīng)當(dāng)為 false;
        urlId = mStringSupport.getStringId("url", false);
        degreeId = mStringSupport.getStringId("degree", false);
    }

由于 ViewBase 本身也是實(shí)現(xiàn) IView 接口的匈棘,所以復(fù)寫(xiě)幾個(gè) IView 的 measure鹃愤、layout 接口,去調(diào)用對(duì)應(yīng)的 PicassoImageView 里的接口姿现。在 VirtualView 體系內(nèi)部提佣,都是通過(guò) ViewBase 對(duì)象來(lái)驅(qū)動(dòng)布局計(jì)算的,因此必須通過(guò) IView 接口調(diào)用系統(tǒng) View 真正的計(jì)算接口。

@Override
public void onComMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mPicassoImageView.onComMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
public void onComLayout(boolean changed, int l, int t, int r, int b) {
    mPicassoImageView.onComLayout(changed, l, t, r, b);
}

@Override
public void comLayout(int l, int t, int r, int b) {
    super.comLayout(l, t, r, b);
    //這一步很關(guān)鍵,否則 view 不顯示刑巧。
    mPicassoImageView.comLayout(l, t, r, b);
}

剩下的主要邏輯是處理自定義屬性浑彰,有幾個(gè) setAttributesetRPAttribute 重載的方法,它們用于接收不同類型的屬性值:

  • boolean setAttribute(int key, int value) 處理編譯成整數(shù)類型的屬性;
  • boolean setAttribute(int key, float value) 處理編譯成浮點(diǎn)數(shù)類型的屬性;
  • boolean setAttribute(int key, String stringValue) 處理編譯成字符串類型的屬性,包括那些本該編譯成整數(shù)或者浮點(diǎn)數(shù)但因?yàn)閷?xiě)了表達(dá)式被編譯成字符串類型的潭枣;
  • boolean setRPAttribute(int key, int value) 處理編譯成整數(shù)類型的尺寸屬性篡九,單位是 rp(介紹在此);
  • boolean setRPAttribute(int key, float value) 處理編譯成浮點(diǎn)數(shù)類型的尺寸屬性金刁,單位是 rp;

基礎(chǔ) ViewBase 里解析處理了大量基礎(chǔ)屬性,所以自定義控件只要處理新增的自定義屬性就行了。以上這些重載方法都有一個(gè) Boolean 返回值,它遵循冒泡邏輯,當(dāng)你返回 true 的時(shí)候泊脐,當(dāng)前層級(jí)處理了這個(gè)屬性约郁,否則表示當(dāng)前層級(jí)處理不了這個(gè)屬性绽快,需要進(jìn)一步交給子類解析活孩;在本文的示例里蕉朵,是這么處理的:

@Override
protected boolean setAttribute(int key, float value) {
    boolean ret = true;
    if (key == degreeId) {
        //從模板里直接獲取到旋轉(zhuǎn)角度屬性值
        degrees = value;
    } else {
        ret = super.setAttribute(key, value);
    }
    return ret;
}

@Override
protected boolean setAttribute(int key, String stringValue) {
    boolean ret = true;
    if (key == degreeId) {
        //從模板里直接獲取到旋轉(zhuǎn)角度屬性值是一個(gè)表達(dá)式蝙茶,暫存到 viewCache 里蹄衷,等傳入數(shù)據(jù)的時(shí)候再次解析托嚣,然后回調(diào)到上述 setAttribute(int key, float value) 方法里獲取最終值
        if (Utils.isEL(stringValue)) {
            mViewCache.put(this, degreeId, stringValue, Item.TYPE_FLOAT);
        }
    } else if (key == urlId) {
        //從模板里直接獲取到url屬性值可能是一個(gè)表達(dá)式啤月,也可能是個(gè)直接的 url杉武,如果是表達(dá)式,暫存到 viewCache 里蘸秘,等傳入數(shù)據(jù)的時(shí)候再次解析,然后回調(diào)本方法里獲取最終值
        if (Utils.isEL(stringValue)) {
            mViewCache.put(this, urlId, stringValue, Item.TYPE_STRING);
        } else {
            url = stringValue;
        }
    } else {
        ret = super.setAttribute(key, stringValue);
    }
    return ret;
}

最后就是使用這些屬性值霞揉,在 onParseValueFinised() 里一次性應(yīng)用屬性:

@Override
public void onParseValueFinished() {
    super.onParseValueFinished();
    Picasso.with(mContext.getContext()).load(url).rotate(degrees).into(mPicassoImageView);
}

詳細(xì)代碼:PicassoImage.java

4. 注冊(cè)控件

通過(guò) ViewManager 里的 ViewFactory 注冊(cè)阶捆,如下:

sViewManager.getViewFactory().registerBuilder(1014,new PicassoImage.Builder());

5. 使用與運(yùn)行效果

XML 里這么寫(xiě):

<VHLayout
    flag="flag_exposure|flag_clickable"
    orientation="V"
    layoutWidth="match_parent"
    layoutHeight="match_parent"
>
<VText
        text="Title: Loading Image with Picasso"
        textSize="12"
        textColor="#333333"
        background="#008899"
        layoutWidth="match_parent"
        layoutHeight="20" />

<PicassoImage
        url="${url}"
        degree="90"
        layoutWidth="match_parent"
        layoutHeight="300" />
</VHLayout>

綁定的數(shù)據(jù):

{
  "url": "https://gw.alicdn.com/tfs/TB1l0HSgvxNTKJjy0FjXXX6yVXa-200-200.png"
}

運(yùn)行的結(jié)果:

image

圖片原圖是這樣的:

image

可以看到淮韭,通過(guò)添加自定義的 degree 屬性,并調(diào)用 Picasso 的 ratate 方法牲距,最終加載了圖片,也旋轉(zhuǎn)了圖片择同,可以根據(jù)此思路繼續(xù)為 PicassImage 添加更多 Picasso 支持的屬性。

本文里用到的例子也上傳到了 demo 里滥酥,從上午的源碼鏈接里可以獲取到完整的 demo诸尽。

體驗(yàn)一下

還是那句話,講得再多,不如親自上手體驗(yàn)一下酱酬,可以參考《天貓客戶端組件動(dòng)態(tài)化的方案——VirtualView 上手體驗(yàn)》痛阻、《提升開(kāi)發(fā)體驗(yàn),預(yù)覽 VirtualView》來(lái)體驗(yàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贴膘,一起剝皮案震驚了整個(gè)濱河市刊懈,隨后出現(xiàn)的幾起案子殉疼,更是在濱河造成了極大的恐慌,老刑警劉巖托酸,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泉唁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宝踪,警方通過(guò)查閱死者的電腦和手機(jī)厉膀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)斟珊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堵漱,“玉大人综慎,你說(shuō)我怎么就攤上這事≌浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)埃元。 經(jīng)常有香客問(wèn)我涝涤,道長(zhǎng),這世上最難降的妖魔是什么岛杀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任阔拳,我火速辦了婚禮,結(jié)果婚禮上类嗤,老公的妹妹穿的比我還像新娘糊肠。我一直安慰自己,他們只是感情好遗锣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布货裹。 她就那樣靜靜地躺著,像睡著了一般精偿。 火紅的嫁衣襯著肌膚如雪弧圆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天笔咽,我揣著相機(jī)與錄音搔预,去河邊找鬼。 笑死叶组,一個(gè)胖子當(dāng)著我的面吹牛拯田,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甩十,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼船庇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了枣氧?” 一聲冷哼從身側(cè)響起溢十,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎达吞,沒(méi)想到半個(gè)月后张弛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酪劫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年吞鸭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片覆糟。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刻剥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滩字,到底是詐尸還是另有隱情造虏,我是刑警寧澤御吞,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站漓藕,受9級(jí)特大地震影響陶珠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜享钞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一揍诽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栗竖,春花似錦暑脆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至处坪,卻和暖如春根资,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背同窘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工玄帕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人想邦。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓裤纹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親丧没。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹰椒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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

  • 溫和。溫和呕童。溫和漆际。 目錄【怎么洗】1.RoX - 給干性及所有皮膚的清潔建議2.RoX - 臉是怎么爛掉的3.針線...
    易烊千璽的老公閱讀 1,001評(píng)論 0 0
  • 一.什么是積極主動(dòng)? 每一個(gè)人都應(yīng)該積極地創(chuàng)造有利于自己發(fā)展的外部環(huán)境夺饲,并通過(guò)自我意識(shí)(理性我)來(lái)主動(dòng)做出選擇奸汇,對(duì)...
    許楷模閱讀 188評(píng)論 1 2
  • 2017年12月16日下午擂找,在正安聚友會(huì)蘇州分會(huì)譚會(huì)長(zhǎng)的組織下,有緣樁友滙集恕閣浩销,由張石山老師帶領(lǐng)贯涎,學(xué)習(xí)交流了形意...
    如心1976閱讀 453評(píng)論 2 4
  • 清晨起,睜著迷迷糊糊的眼睛慢洋,爬起床來(lái)塘雳,揣著手就悠悠的去上早讀了陆盘。 走出宿舍大門(mén),“呼”一陣?yán)溧侧驳娘L(fēng)吹來(lái)败明,脖子縮的...
    卷腹小豬豬閱讀 285評(píng)論 0 1
  • 故地重游 作者:一抹詩(shī)痕小窗楓雨煙波色礁遣,往事經(jīng)年入紙堆。日暮寒秋身寄客肩刃,風(fēng)霜草木歲痕摧。重游故地秋時(shí)景杏头,落葉傷懷憶...
    隨意詩(shī)社閱讀 1,458評(píng)論 0 1