美文网首页
【UI篇1】自定义Preference

【UI篇1】自定义Preference

作者: 农民工Alan | 来源:发表于2022-01-13 19:31 被阅读0次

写在前面的话
学而不思则罔,思而不学则殆。最近在做设置页的UI调整,总会遇到需要自定义Preference的情况,现将自定义Preference的一些思考总结如下。既方面后续查阅,也与大家交流一下自己的看法,希望通过交流可以有更大的进步。

关于自定义Preference,一般会遇到两种场景:一种是可以复用原生Preference布局;另一种是layout完全无法复用,需要自己定义layout。下面分别针对这两种情况给出自己的总结。

1、可复用Preference布局

1.1 分析

针对可复用Preference布局的情况,首先我们看下preference/preference/res/layout/preference.xml 的布局如下所示,此处可以替换的布局id为widget_frame,Preference类提供了方法setWidgetLayoutResource(int widgetLayoutResid)来设置该布局。

<?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>

这里我们用一个ButtonPreference来举例,用Button填充widget_frame来实现ButtonPreference,实现功能:设置ButtonPreference中Button上的Text,具体实现如下

  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. 构造方法中通过setWidgetlayoutResource方法替换布局
  2. 继承Preference,实现onBindViewHolder方法,获取自定义布局中的控件
  3. 实现setButtonText方法和点击监听
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 总结

看完上面的流程和代码,我们总结提炼一下实现过程五步法:

  1. 定义要替换的布局文件R.layout.preference_widget_button
  2. 定义所需的属性,如定义buttonText属性
  3. 构造方法中通过setWidgetlayoutResource方法替换布局
  4. 继承Preference,实现onBindViewHolder方法,获取自定义布局中的控件
  5. 实现setButtonText方法和点击监听

2、layout无法复用

2.2 分析

说完可以复用的场景,这里针对不能复用Preference布局的情况,这里我们以LoadingPreference举例进行说明,具体实现如下:

  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,实现onBindViewHolder方法,初始化需要用到的控件
  2. 设置属性
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 总结

看完上面的流程和代码,我们总结提炼一下实现过程四步法:

  1. 自定义layout
  2. xml中声明LoadingPreference属性android:layout为自定义的布局
  3. 继承Preference,实现onBindViewHolder方法,初始化需要用到的控件
  4. 设置属性,实现控件属性功能,需要更新执行notifyChaned方法

3. 扩展

3.1 扩展xml属性

以上面的ButtonPreference举例,上面定义buttonText属性后,在xml中即可以用到

属性名称 描述 类型 示例
buttonText 设置按钮的文字内容 string app:buttonText="上传"
jump_mark 资源设置 reference app:jump_mark="@drawable/next"

3.2 扩展api

以上面的ButtonPreference举例,上面定义的fun setButtonText(text: Charsequence?)即为扩展api

4. Preference的种类

切记:在造轮子之前,我们一定要了解是否有该轮子,切莫乱造轮子,复用一堆垃圾代码,下面列出目前api中有的Preference

种类
Preference
CheckBoxPreference
ListPreference
SeekBarPreference
DialogPreference
SwitchPreference
DropDownPreference
EditTextPreference
MultiSelectListPreference
TwoStatePreference

相关文章

网友评论

      本文标题:【UI篇1】自定义Preference

      本文链接:https://www.haomeiwen.com/subject/mpatpltx.html