Android自定義View之組合控件

簡介

在Android開發(fā)中除了View的自定義繪制以外加叁,組合控件也是比較常見的一種自定義View的方式哥力,這種方式通過把系統(tǒng)提供的一些常見View組合起來蔗怠,添加一些自定義的屬性;很少或者根本不需要進行手動的額外繪制省骂,操作相對簡單一些蟀淮,也能大幅度提升開發(fā)效率。

在絕大多數app中都有這樣的頁面:


常見頁面

需求整理

從上圖我們可以看到這種常見的UI效果中包含了幾種情況:

1.選擇效果钞澳,例如時間的選擇怠惶,點擊右邊彈出選擇框或者頁面跳轉,整個item是不可編輯的轧粟。
2.輸入效果策治,左邊是顯示輸入框的文本屬性,右邊是輸入框兰吟,用戶可以輸入文本通惫,例如修改個人信息的時候會用到。
3.顯示效果混蔼,左右都不可編輯履腋,只是單純的顯示一些信息。

除此之外還有一些細節(jié)方面的屬性惭嚣,例如:左邊和右邊文本的屬性遵湖,顏色,大小晚吞,padding值等延旧;item之間的divider分隔線,分隔線的顏色槽地,寬度迁沫,padding值芦瘾;文本框的hint以及hint的細節(jié)屬性;文字的Gravity屬性以及item的點擊事件響應等等集畅。

之前為了實現這些效果近弟,大多數人常用的方法就是直接在UI里面進行系統(tǒng)控件的嵌套組合;這是一種最直接也最簡單的方法牡整。但是如果類似的效果少了還好藐吮,多的話就讓人比較頭疼了,只能一遍一遍復制粘貼逃贝。這樣就會造成頁面布局文件很長很長谣辞;系統(tǒng)解析起來費勁,自己看著也煩沐扳。

這時候自定義組合控件就派上用場了泥从。

實現原理

先通過分析,把類似的效果抽取到一個通用的UI中沪摄。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_view_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_choose_item_name"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:padding="15dp"
        android:textColor="@android:color/black"/>

    <TextView
        android:id="@+id/tv_choose_item_choose"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="100"
        android:drawablePadding="15dp"
        android:maxLines="1"
        android:ellipsize="end"
        android:padding="15dp"
        android:textColor="@android:color/black"/>

    <EditText
        android:id="@+id/et_choose_item_choose"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="100"
        android:background="@null"
        android:paddingLeft="15dp"
        android:paddingBottom="15dp"
        android:paddingTop="15dp"
        android:paddingRight="50dp"
        android:maxLines="1"
        android:ellipsize="end"
        android:textColor="@android:color/black"
        android:visibility="gone"/>
</LinearLayout>

總的來說就是一個TextView+一個EditText的組合躯嫉,或者兩個TextView的組合;或者可以把右箭頭圖標單獨拎出來放進一個ImageView中杨拐。

布局抽取出來之后就開始進行控件組合代碼的編寫:

先繼承LinearLayout并添加構造方法祈餐,添加自定義的屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ChooseItemView">
        <attr name="item_name_text" format="string|reference"/><!--item名稱-->
        <attr name="item_name_text_padding_left" format="dimension|reference"/>
        <attr name="item_height" format="dimension|reference"/>
        <attr name="item_name_text_color" format="color|reference"/>
        <attr name="item_name_text_size" format="dimension|reference"/>
        <attr name="item_choose_text" format="string|reference"/>
        <attr name="item_choose_text_color" format="color|reference"/>
        <attr name="item_choose_text_size" format="dimension|reference"/>
        <attr name="item_choose_icon_visible" format="boolean"/>
        <attr name="item_edit_mode" format="boolean"/>
        <attr name="item_edit_hint" format="string|reference"/>
        <attr name="item_edit_text" format="string|reference"/>
        <attr name="item_edit_text_size" format="dimension|reference"/>
        <attr name="item_edit_text_color" format="color|reference"/>
        <attr name="item_edit_hint_color" format="color|reference"/>
        <attr name="item_divider_color" format="color|reference"/>
        <attr name="item_divider_height" format="reference|dimension"/>
        <attr name="item_divider_margin_left" format="reference|dimension"/>
        <attr name="item_divider_margin_right" format="reference|dimension"/>
        <attr name="item_choose_gravity" format="enum">
            <enum name="left" value="0"/>
            <enum name="center" value="1"/>
            <enum name="right" value="2"/>
        </attr>
        <attr name="item_edit_text_gravity" format="enum">
            <enum name="left" value="0"/>
            <enum name="center" value="1"/>
            <enum name="right" value="2"/>
        </attr>
    </declare-styleable>
</resources>
public class ChooseItemView extends LinearLayout
{
  
    public ChooseItemView(Context context)
    {
        this(context, null, 0);
    }

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

    public ChooseItemView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        this.context = context;
        //抽取需要的自定義屬性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChooseItemView);
        itemNameText = typedArray.getString(R.styleable.ChooseItemView_item_name_text);
        itemNameTextPaddingLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_padding_left, dip2px(15));
        itemNameTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_name_text_color, Color.BLACK);
        itemNameTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_size, 16);
        itemChooseText = typedArray.getString(R.styleable.ChooseItemView_item_choose_text);
        itemChooseTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_choose_text_color, Color.BLACK);
        itemChooseTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_choose_text_size, 16);
        itemChooseGravity = typedArray.getInt(R.styleable.ChooseItemView_item_choose_gravity, 2);
        editTextGravity = typedArray.getInt(R.styleable.ChooseItemView_item_edit_text_gravity, 2);
        iconVisible = typedArray.getBoolean(R.styleable.ChooseItemView_item_choose_icon_visible, true);

        editMode = typedArray.getBoolean(R.styleable.ChooseItemView_item_edit_mode, false);
        editHint = typedArray.getString(R.styleable.ChooseItemView_item_edit_hint);
        editHintColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_hint_color, Color.LTGRAY);
        editText = typedArray.getString(R.styleable.ChooseItemView_item_edit_text);
        editTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_text_color, Color.BLACK);
        editTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_edit_text_size, 16);

        dividerColor = typedArray.getColor(R.styleable.ChooseItemView_item_divider_color, Color.LTGRAY);
        dividerHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_height, dip2px(2));
        dividerMarinLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_left, dip2px(15));
        dividerMarinRight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_right, 0);
        itemHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_height, dip2px(50))+dividerHeight/2;

        typedArray.recycle();
  
    }

進行子View的綁定和屬性初始化:

    private void initView()
    {
        //準備繪制分隔線的畫筆
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(dividerColor);
        paint.setStrokeWidth(dividerHeight);
        
        //子控件綁定
        View view = LayoutInflater.from(context).inflate(R.layout.layout_choose_item, this);

        llContainer = (LinearLayout) view.findViewById(R.id.ll_view_container);
        tvItemName = (TextView) view.findViewById(R.id.tv_choose_item_name);
        tvItemChoose = (TextView) view.findViewById(R.id.tv_choose_item_choose);
        etItemChoose = (EditText) view.findViewById(R.id.et_choose_item_choose);

        //初始化各個控件的屬性
        tvItemName.setText(itemNameText);
        tvItemName.setTextColor(itemNameTextColor);
        tvItemName.setTextSize(itemNameTextSize);
        tvItemName.setPadding(itemNameTextPaddingLeft, dip2px(15), dip2px(15), dip2px(15));


        tvItemChoose.setText(itemChooseText);
        tvItemChoose.setTextColor(itemChooseTextColor);
        tvItemChoose.setTextSize(itemChooseTextSize);

        //item整體高度的自定義
        ViewGroup.LayoutParams params = llContainer.getLayoutParams();
        params.height = itemHeight;
        llContainer.setLayoutParams(params);

        etItemChoose.setText(editText);
        etItemChoose.setHint(editHint);
        etItemChoose.setHintTextColor(editHintColor);
        etItemChoose.setTextSize(editTextSize);
        etItemChoose.setTextColor(editTextColor);

        setEditMode(editMode);
        setIconVisible(iconVisible);
        setItemChooseGravity(itemChooseGravity);
        setEditTextGravity(editTextGravity);

    }

繪制item之間的分隔線:

    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        //繪制分隔線 一定要在父控件繪制完成之后
        super.dispatchDraw(canvas);
        canvas.drawLine(dividerMarinLeft, itemHeight, getWidth() - dividerMarinRight, itemHeight, paint);
    }

添加一些常用的api以便在代碼中修改相關屬性:

    //自定義屬性相關的api
    public String getItemNameText()
    {
        return itemNameText;
    }

    public void setItemNameText(String itemNameText)
    {
        this.itemNameText = itemNameText;
        tvItemName.setText(itemNameText);
    }
    public void setItemNameTextSize(int itemNameTextSize)
    {
        this.itemNameTextSize = itemNameTextSize;
        tvItemName.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemNameTextSize);
    }

    public void setItemChooseTextColor(int itemChooseTextColor)
    {
        this.itemChooseTextColor = itemChooseTextColor;
        tvItemChoose.setTextColor(ContextCompat.getColor(context, itemChooseTextColor));
    }

    public int getItemChooseTextSize()
    {
        return itemChooseTextSize;
    }

    public void setItemChooseTextSize(int itemChooseTextSize)
    {
        this.itemChooseTextSize = itemChooseTextSize;
        tvItemChoose.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemChooseTextSize);
    }
    //與繪制相關的屬性修改完成后要記得通知系統(tǒng)進行重繪
    public void setDividerColor(int dividerColor)
    {
        this.dividerColor = dividerColor;
        paint.setColor(ContextCompat.getColor(context, dividerColor));
        invalidate();
    }
    //根據需要添加對應的監(jiān)聽事件
    public void setIconClickListener(OnClickListener iconClickListener)
    {
        tvItemChoose.setOnClickListener(iconClickListener);
    }

    public void setTextChangeListener(TextWatcher textChangeListener)
    {
        etItemChoose.addTextChangedListener(textChangeListener);
    }
}
....

效果和總結

使用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff"
    android:orientation="vertical">

    <com.mewlxy.itemchooseview.view.ChooseItemView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:item_choose_text="2017-08-27"
        app:item_name_text="選擇時間"/>

    <com.mewlxy.itemchooseview.view.ChooseItemView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:item_edit_hint="請輸入姓名"
        app:item_edit_mode="true"
        app:item_name_text="輸入姓名"/>

    <com.mewlxy.itemchooseview.view.ChooseItemView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:item_divider_color="@android:color/holo_purple"
        app:item_divider_height="10dp"
        app:item_divider_margin_left="0dp"
        app:item_edit_hint="請輸入姓名"
        app:item_edit_hint_color="#66ccbb"
        app:item_edit_mode="true"
        app:item_name_text="輸入姓名"
        app:item_name_text_color="#66ccff"/>

    <com.mewlxy.itemchooseview.view.ChooseItemView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:item_choose_gravity="left"
        app:item_choose_icon_visible="false"
        app:item_choose_text="姓名"
        app:item_choose_text_color="@color/colorAccent"
        app:item_divider_margin_left="0dp"
        app:item_edit_hint_color="#66ccbb"
        app:item_name_text="輸入姓名"
        app:item_name_text_color="#66ccff"/>

</LinearLayout>
效果.png

這樣完了之后,一個通用的UI控件就完成了哄陶,可以適用于大多數類似的頁面帆阳,僅僅需要設置幾個不同的屬性;成倍的減少了代碼量屋吨,布局文件再也不會像以前那樣臃腫了蜒谤。并且代碼的擴展性也增強了不少。以后遇到類似的場景就可以參考這種實現方式至扰,而不必一次次的復制粘貼鳍徽。

完整代碼參考

GitHub

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市敢课,隨后出現的幾起案子阶祭,更是在濱河造成了極大的恐慌,老刑警劉巖直秆,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胖翰,死亡現場離奇詭異,居然都是意外死亡切厘,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門懊缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疫稿,“玉大人培他,你說我怎么就攤上這事∫抛” “怎么了舀凛?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長途蒋。 經常有香客問我猛遍,道長,這世上最難降的妖魔是什么号坡? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任懊烤,我火速辦了婚禮,結果婚禮上宽堆,老公的妹妹穿的比我還像新娘腌紧。我一直安慰自己,他們只是感情好畜隶,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布壁肋。 她就那樣靜靜地躺著,像睡著了一般籽慢。 火紅的嫁衣襯著肌膚如雪浸遗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天箱亿,我揣著相機與錄音跛锌,去河邊找鬼。 笑死极景,一個胖子當著我的面吹牛察净,可吹牛的內容都是我干的。 我是一名探鬼主播盼樟,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼氢卡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晨缴?” 一聲冷哼從身側響起蓝撇,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤碉考,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體魏宽,經...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年着逐,在試婚紗的時候發(fā)現自己被綠了倘潜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡械拍,死狀恐怖突勇,靈堂內的尸體忽然破棺而出装盯,到底是詐尸還是另有隱情,我是刑警寧澤甲馋,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布埂奈,位于F島的核電站,受9級特大地震影響定躏,放射性物質發(fā)生泄漏账磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一痊远、第九天 我趴在偏房一處隱蔽的房頂上張望垮抗。 院中可真熱鬧,春花似錦拗引、人聲如沸借宵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壤玫。三九已至,卻和暖如春哼凯,著一層夾襖步出監(jiān)牢的瞬間欲间,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工断部, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猎贴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓蝴光,卻偏偏與公主長得像她渴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔑祟,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,841評論 25 707
  • 關在黑屋子里太久趁耗,漸漸開始不習慣陽光的味道,那刺眼的溫暖也成了曝光的負累疆虚。有時候會糾結苛败,自己到底是屬于哪一個領域的...
    夜白安閱讀 260評論 0 0
  • 今天按照書上說的Q版漫畫畫了幾個人臉~ —— ——! Fathing
    魯狂歌閱讀 122評論 0 0
  • 第一、問過多問題 或者是搭訕焦慮篇亭、缺少話題缠捌、等原因,男生喜歡在和女生互動的時候問過多的問題译蒂,并且無意識地把話題的主...
    繁越S閱讀 312評論 0 0
  • 夏天除了溫度高鄙币,最煩人的就是下雨天肃叶,而且一下就是好幾天,最可憐的是上班族十嘿,一場暴雨過后,再美的穿搭也變得毫無亮點岳锁。...
    蜘蜘紡閱讀 343評論 0 0