【UI篇1】自定義Preference

寫在前面的話
學(xué)而不思則罔支竹,思而不學(xué)則殆屹培。最近在做設(shè)置頁的UI調(diào)整涮阔,總會(huì)遇到需要自定義Preference的情況滔灶,現(xiàn)將自定義Preference的一些思考總結(jié)如下瑞眼。既方面后續(xù)查閱夺英,也與大家交流一下自己的看法明垢,希望通過交流可以有更大的進(jìn)步神得。

關(guān)于自定義Preference奈籽,一般會(huì)遇到兩種場景:一種是可以復(fù)用原生Preference布局饥侵;另一種是layout完全無法復(fù)用,需要自己定義layout衣屏。下面分別針對這兩種情況給出自己的總結(jié)躏升。

1、可復(fù)用Preference布局

1.1 分析

針對可復(fù)用Preference布局的情況狼忱,首先我們看下preference/preference/res/layout/preference.xml 的布局如下所示膨疏,此處可以替換的布局id為widget_frame,Preference類提供了方法setWidgetLayoutResource(int widgetLayoutResid)來設(shè)置該布局钻弄。

<?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="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:paddingEnd="?android:attr/scrollbarSize"
    android:paddingRight="?android:attr/scrollbarSize"
    android:background="?android:attr/selectableItemBackground">

    <FrameLayout
        android:id="@+id/icon_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <androidx.preference.internal.PreferenceImageView
            android:id="@android:id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:maxWidth="48dp"
            app:maxHeight="48dp" />
    </FrameLayout>

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dip"
        android:layout_marginLeft="15dip"
        android:layout_marginEnd="6dip"
        android:layout_marginRight="6dip"
        android:layout_marginTop="6dip"
        android:layout_marginBottom="6dip"
        android:layout_weight="1">

        <TextView android:id="@android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="?android:attr/textColorPrimary"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal" />

        <TextView android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:layout_alignStart="@android:id/title"
            android:layout_alignLeft="@android:id/title"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="?android:attr/textColorSecondary"
            android:maxLines="4" />

    </RelativeLayout>

    <!-- Preference should place its actual preference widget here. -->
    <!-- 可以替換的布局控件. -->
    <LinearLayout android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:orientation="vertical" />

</LinearLayout>

這里我們用一個(gè)ButtonPreference來舉例佃却,用Button填充widget_frame來實(shí)現(xiàn)ButtonPreference,實(shí)現(xiàn)功能:設(shè)置ButtonPreference中Button上的Text窘俺,具體實(shí)現(xiàn)如下

  1. 定義要替換的Button布局文件 R.layout.preference_widget_button
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/buttonWidget"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />
  1. 定義buttonText屬性
<declare-styleable name="ButtonPreference">
    <attr name="buttonText" format="string" />
</declare-styleable>
  1. 構(gòu)造方法中通過setWidgetlayoutResource方法替換布局
  2. 繼承Preference饲帅,實(shí)現(xiàn)onBindViewHolder方法,獲取自定義布局中的控件
  3. 實(shí)現(xiàn)setButtonText方法和點(diǎn)擊監(jiān)聽
class ButtonPreference : Preference {
    private var button: Button? = null
    private var buttonText: CharSequence? = ""
    private var buttonClickListener: OnButtonClickListener? = null

    interface OnButtonClickListener {
        fun onButtonClick(view: View?)
    }

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(
        context,
        attrs,
        R.attr.PreferenceStyle
    )

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(
        context,
        attrs,
        defStyleAttr,
        0
    )

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr) {
       //替換布局
        widgetLayoutResource = R.layout.preference_widget_button
        val a = context.obtainStyledAttributes(
            attrs,
            R.styleable.ButtonPreference,
            defStyleAttr,
            defStyleRes
        )
        //獲取屬性
        buttonText = a.getText(R.styleable.ButtonPreference_buttonText)
        a.recycle()
    }

    override fun onBindViewHolder(holder: PreferenceViewHolder) {
        super.onBindViewHolder(holder)
        //初始化控件
        button = holder.findViewById(R.id.buttonWidget) as? Button
        button?.apply {
            setOnClickListener { v -> buttonClickListener?.onButtonClick(v) }
            if (!buttonText.isNullOrEmpty()) {
                text = buttonText
            }
        }
    }

    fun setButtonText(text: CharSequence?) {
        Log.d("setButtonText", "text=$text")
        if (!TextUtils.equals(text, buttonText)) {
            buttonText = text
            notifyChanged()
        }
    }

    fun setOnButtonClickListener(listener: OnButtonClickListener) {
        buttonClickListener = listener
    }
}

1.2 總結(jié)

看完上面的流程和代碼瘤泪,我們總結(jié)提煉一下實(shí)現(xiàn)過程五步法:

  1. 定義要替換的布局文件R.layout.preference_widget_button
  2. 定義所需的屬性灶泵,如定義buttonText屬性
  3. 構(gòu)造方法中通過setWidgetlayoutResource方法替換布局
  4. 繼承Preference,實(shí)現(xiàn)onBindViewHolder方法对途,獲取自定義布局中的控件
  5. 實(shí)現(xiàn)setButtonText方法和點(diǎn)擊監(jiān)聽

2丘逸、layout無法復(fù)用

2.2 分析

說完可以復(fù)用的場景,這里針對不能復(fù)用Preference布局的情況掀宋,這里我們以LoadingPreference舉例進(jìn)行說明深纲,具體實(shí)現(xiàn)如下:

  1. 自定義layout為R.layout.loading_preference_layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:oppo="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:orientation="vertical"
    android:background="@drawable/preference_bg_selector"
    android:minHeight="@dimen/loading_preference_min_height"
    android:paddingStart="@dimen/preference_titel_padding_start"
    android:paddingEnd="@dimen/preference_titel_padding_end"
    android:paddingTop="@dimen/preference_text_content_padding_top"
    android:paddingBottom="@dimen/preference_text_content_padding_bottom">

    <TextView
        android:id="@android:id/title"
        style="@style/PreferenceTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="@dimen/list_view_item_text_size"/>

    <TextView
        android:id="@android:id/summary"
        style="@style/PreferenceSummary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/preference_margin_between_line" />

    <TextView
        android:id="@+id/assignment"
        style="@style/Assignment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/preference_margin_between_line" />

    <LoadingView
        android:id="@+id/loadingView"
        style="@style/PreferenceLoadingView"
        android:layout_width="@dimen/loading_preference_item_min_size"
        android:layout_height="@dimen/loading_preference_item_min_size"
        android:layout_marginTop="@dimen/preference_margin_between_line"
        android:visibility="gone" />

</LinearLayout>
  1. PreferenceScreen中定義LoadingPreference,聲明屬性android:layout為自定義的布局
<LoadingPreference
    android:key="version_update"
    android:title="version_name"
    android:layout="@layout/loading_preference_layout">
</LoadingPreference>
  1. 繼承Preference劲妙,實(shí)現(xiàn)onBindViewHolder方法湃鹊,初始化需要用到的控件
  2. 設(shè)置屬性
class LoadingPreference : Preference {
    private var loadingView: LoadingView? = null
    private var assignment: TextView? = null

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?)
            : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
            : this(context, attrs, defStyleAttr, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int)
            : super(context, attrs, defStyleAttr)

    override fun onBindViewHolder(holder: PreferenceViewHolder) {
        super.onBindViewHolder(holder)
        loadingView = holder.findViewById(R.id.loadingView) as LoadingView
        assignment = holder.findViewById(R.id.assignment) as TextView
    }

    fun setLoadingViewVisibility(visibility: Int){
        loadingView?.visibility = visibility
    }

    fun updateFinish(){
        setAssignment("已更新")
    }
}

2.2 總結(jié)

看完上面的流程和代碼,我們總結(jié)提煉一下實(shí)現(xiàn)過程四步法:

  1. 自定義layout
  2. xml中聲明LoadingPreference屬性android:layout為自定義的布局
  3. 繼承Preference镣奋,實(shí)現(xiàn)onBindViewHolder方法币呵,初始化需要用到的控件
  4. 設(shè)置屬性,實(shí)現(xiàn)控件屬性功能,需要更新執(zhí)行notifyChaned方法

3. 擴(kuò)展

3.1 擴(kuò)展xml屬性

以上面的ButtonPreference舉例,上面定義buttonText屬性后余赢,在xml中即可以用到

屬性名稱 描述 類型 示例
buttonText 設(shè)置按鈕的文字內(nèi)容 string app:buttonText="上傳"
jump_mark 資源設(shè)置 reference app:jump_mark="@drawable/next"

3.2 擴(kuò)展api

以上面的ButtonPreference舉例芯义,上面定義的fun setButtonText(text: Charsequence?)即為擴(kuò)展api

4. Preference的種類

切記:在造輪子之前,我們一定要了解是否有該輪子妻柒,切莫亂造輪子扛拨,復(fù)用一堆垃圾代碼,下面列出目前api中有的Preference

種類
Preference
CheckBoxPreference
ListPreference
SeekBarPreference
DialogPreference
SwitchPreference
DropDownPreference
EditTextPreference
MultiSelectListPreference
TwoStatePreference
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末举塔,一起剝皮案震驚了整個(gè)濱河市绑警,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌央渣,老刑警劉巖计盒,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芽丹,居然都是意外死亡北启,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門拔第,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暖庄,“玉大人,你說我怎么就攤上這事楼肪∨嗬” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵春叫,是天一觀的道長肩钠。 經(jīng)常有香客問我,道長暂殖,這世上最難降的妖魔是什么价匠? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮呛每,結(jié)果婚禮上踩窖,老公的妹妹穿的比我還像新娘。我一直安慰自己晨横,他們只是感情好洋腮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著手形,像睡著了一般啥供。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上库糠,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天伙狐,我揣著相機(jī)與錄音,去河邊找鬼。 笑死贷屎,一個(gè)胖子當(dāng)著我的面吹牛罢防,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唉侄,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼咒吐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了美旧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤贬墩,失蹤者是張志新(化名)和其女友劉穎榴嗅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陶舞,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗽测,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肿孵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠粥。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖停做,靈堂內(nèi)的尸體忽然破棺而出晤愧,到底是詐尸還是另有隱情,我是刑警寧澤蛉腌,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布官份,位于F島的核電站,受9級(jí)特大地震影響烙丛,放射性物質(zhì)發(fā)生泄漏舅巷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一河咽、第九天 我趴在偏房一處隱蔽的房頂上張望钠右。 院中可真熱鬧,春花似錦忘蟹、人聲如沸飒房。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽情屹。三九已至,卻和暖如春杂腰,著一層夾襖步出監(jiān)牢的瞬間垃你,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惜颇,地道東北人皆刺。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像凌摄,于是被迫代替她去往敵國和親羡蛾。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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