寫在前面的話
學(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)如下
- 定義要替換的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" />
- 定義buttonText屬性
<declare-styleable name="ButtonPreference">
<attr name="buttonText" format="string" />
</declare-styleable>
- 構(gòu)造方法中通過setWidgetlayoutResource方法替換布局
- 繼承Preference饲帅,實(shí)現(xiàn)onBindViewHolder方法,獲取自定義布局中的控件
- 實(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)過程五步法:
- 定義要替換的布局文件R.layout.preference_widget_button
- 定義所需的屬性灶泵,如定義buttonText屬性
- 構(gòu)造方法中通過setWidgetlayoutResource方法替換布局
- 繼承Preference,實(shí)現(xiàn)onBindViewHolder方法对途,獲取自定義布局中的控件
- 實(shí)現(xiàn)setButtonText方法和點(diǎn)擊監(jiān)聽
2丘逸、layout無法復(fù)用
2.2 分析
說完可以復(fù)用的場景,這里針對不能復(fù)用Preference布局的情況掀宋,這里我們以LoadingPreference舉例進(jìn)行說明深纲,具體實(shí)現(xiàn)如下:
- 自定義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>
- PreferenceScreen中定義LoadingPreference,聲明屬性android:layout為自定義的布局
<LoadingPreference
android:key="version_update"
android:title="version_name"
android:layout="@layout/loading_preference_layout">
</LoadingPreference>
- 繼承Preference劲妙,實(shí)現(xiàn)onBindViewHolder方法湃鹊,初始化需要用到的控件
- 設(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)過程四步法:
- 自定義layout
- xml中聲明LoadingPreference屬性android:layout為自定義的布局
- 繼承Preference镣奋,實(shí)現(xiàn)onBindViewHolder方法币呵,初始化需要用到的控件
- 設(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 |