自定义Preference

本文探讨了如何自定义Preference,包括PreferenceGroup的使用,以及PreferenceActivity和PreferenceFragment在配置自定义偏好设置时的角色。通过理解其内部持有一个ListView并使用特定的adapter来展现偏好设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Preference、PreferenceActivity,PreferenceFragment

Preference的容器,如PreferenceGroup持有一个ListView对象成员,而adapter是PreferenceGroupAdapter(extends BaseAdapter)

PreferenceGroupAdapter#getView()

public View getView(int position, View convertView, ViewGroup parent) {
        final Preference preference = this.getItem(position);//获取对应的Preference
        // Build a PreferenceLayout to compare with known ones that are cacheable.
        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);

        // If it's not one of the cached ones, set the convertView to null so that 
        // the layout gets re-created by the Preference.
        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 ||
                (getItemViewType(position) == getHighlightItemViewType())) {
            convertView = null;
        }
        View result = preference.getView(convertView, parent);//调用Preference的getView获取view
        if (position == mHighlightedPosition && mHighlightedDrawable != null) {
            ViewGroup wrapper = new FrameLayout(parent.getContext());
            wrapper.setLayoutParams(sWrapperLayoutParams);
            wrapper.setBackgroundDrawable(mHighlightedDrawable);
            wrapper.addView(result);
            result = wrapper;
        }
        return result;
    }

Preference#getView()

/**
     * Gets the View that will be shown in the {@link PreferenceActivity}.
     * 
     * @param convertView The old View to reuse, if possible. Note: You should
     *            check that this View is non-null and of an appropriate type
     *            before using. If it is not possible to convert this View to
     *            display the correct data, this method can create a new View.
     * @param parent The parent that this View will eventually be attached to.
     * @return Returns the same Preference object, for chaining multiple calls
     *         into a single statement.
     * @see #onCreateView(ViewGroup)
     * @see #onBindView(View)
     */
    public View getView(View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = onCreateView(parent);//调用onCreateView去inflate布局
        }
        onBindView(convertView);//将Preference的xml文件的属性应用到View中
        return convertView;
    }

Preference#onCreateView()

/**
     * Creates the View to be shown for this Preference in the
     * {@link PreferenceActivity}. The default behavior is to inflate the main
     * layout of this Preference (see {@link #setLayoutResource(int)}. If
     * changing this behavior, please specify a {@link ViewGroup} with ID
     * {@link android.R.id#widget_frame}.
     * <p>
     * Make sure to call through to the superclass's implementation.
     * 
     * @param parent The parent that this View will eventually be attached to.
     * @return The View that displays this Preference.
     * @see #onBindView(View)
     */
    @CallSuper
    protected View onCreateView(ViewGroup parent) {
        final LayoutInflater layoutInflater =
            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        
        final View layout = layoutInflater.inflate(mLayoutResId, parent, false);//布局为mLayoutResId 
        
        final ViewGroup widgetFrame = (ViewGroup) layout
                .findViewById(com.android.internal.R.id.widget_frame);
        if (widgetFrame != null) {
            if (mWidgetLayoutResId != 0) {//mWidgeLayoutResId可以在子类构造方法中传入一个style
                layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
            } else {
                widgetFrame.setVisibility(View.GONE);
            }
        }
        return layout;
    }
该方法是inflate一个Preference的layout,而想要自定义layout,可以重写这个onCreate方法,也可以不重写,

而在子类构造方法中传入一个参数到Preference的第三个参数defStyleAttr。详细看下面SwichPreference的

解析。



Preference#onBindView()

/**
     * Binds the created View to the data for this Preference.
     * <p>
     * This is a good place to grab references to custom Views in the layout and
     * set properties on them.
     * <p>
     * Make sure to call through to the superclass's implementation.
     * 
     * @param view The View that shows this Preference.
     * @see #onCreateView(ViewGroup)
     */
    @CallSuper
    protected void onBindView(View view) {
        final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);
        if (titleView != null) {
            final CharSequence title = getTitle();//获取title属性值
            if (!TextUtils.isEmpty(title)) {
                titleView.setText(title);
                titleView.setVisibility(View.VISIBLE);
            } else {
                titleView.setVisibility(View.GONE);
            }
        }

        final TextView summaryView = (TextView) view.findViewById(
                com.android.internal.R.id.summary);
        if (summaryView != null) {
            final CharSequence summary = getSummary();//获取summary属性值
            if (!TextUtils.isEmpty(summary)) {
                summaryView.setText(summary);
                summaryView.setVisibility(View.VISIBLE);
            } else {
                summaryView.setVisibility(View.GONE);
            }
        }

        final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
        if (imageView != null) {
            if (mIconResId != 0 || mIcon != null) {
                if (mIcon == null) {
                    mIcon = getContext().getDrawable(mIconResId);
                }
                if (mIcon != null) {
                    imageView.setImageDrawable(mIcon);
                }
            }
            imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
        }

        final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame);
        if (imageFrame != null) {
            imageFrame.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
        }

        if (mShouldDisableView) {
            setEnabledStateOnViews(view, isEnabled());//和Enable属性相关,下面在自定义SwitchPreference时会详细说
        }
    }
该方法在Preference属性改变时,会被调用,因为getView会被调用,因为属性改变后,View也会改变,如一些可见性等。

在自定义Preference时,一定要调用这个方法,因为里面有些被调用到的方法的访问权限是private的,如果不调用这个

父类方法,有些属性就无法生效。


还介绍一个public方法,performClick()

/**
     * Called when a click should be performed.
     * 
     * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
     *            listener should be called in the proper order (between other
     *            processing). May be null.
     * @hide
     */
    public void performClick(PreferenceScreen preferenceScreen) {
        
        if (!isEnabled()) {//当enalble时,此方法无效。enalbe的确认其属性enable只是必要条件
            return;
        }
        
        onClick();//自定义时,最好不要直接重写PerformClick,可以重写这个空方法
        
        if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
            return;//对mOnClickListener的处理可能需要重写该方法
        }
        
        PreferenceManager preferenceManager = getPreferenceManager();
        if (preferenceManager != null) {
            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
                    .getOnPreferenceTreeClickListener();
            if (preferenceScreen != null && listener != null
                    && listener.onPreferenceTreeClick(preferenceScreen, this)) {
                return;
            }
        }
        
        if (mIntent != null) {
            Context context = getContext();
            context.startActivity(mIntent);
        }
    }
该方法是点击该Preference的view时会回调的一个方法,如果必须重写该方法,最好调用一下这个父类方法


Perference的一个构造方法:

public SwitchPreference(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.switchPreferenceStyle);
    }
其中switchPreferenceStyle
<item name="switchPreferenceStyle">@style/Preference.SwitchPreference</item>

而perference.SwitchPreference

<style name="Preference.SwitchPreference">
        <item name="widgetLayout">@layout/preference_widget_switch</item>
        <item name="switchTextOn">@string/capital_on</item>
        <item name="switchTextOff">@string/capital_off</item>
    </style>
其中的widgetlayout就是Perference中的mWidgetLayoutResId。




下面说下SwitchPreference的实现,由此可知道自定义Preference的方法

SwitchPreference extends TwoStatePreference,TwoStatePreference没有定义一些跟view相关的东西,

只是增加了mChecked,mSummaryOff,mSummaryOn字段及其getter和setter。而在setter实现中,没有实现对View

的任何操作。其实在Perference的实现中,属性的setter中也没有对View操作,只是将设置进来的东西保存到成员

中,然后notifyChange()通知一个Listener,就是OnPerferenceChangeInternalListener,这是一个默认权限

的接口,而其setter也是,所以只有同一个包的才能设置这个接口,所以我们无法通过子类去设置或者重写这个Listener。

而PreferenceGroupAdapter是实现并为该Group的每个Preference设置了这个Listener。

其实现如下:

public void onPreferenceChange(Preference preference) {
        notifyDataSetChanged();
    }

所以每次有什么属性变化,都会ListView的Adapter都会调用所有Preference#getView一遍。

所以在自定义时,需要在onBindView中处理自定义Preference中新增属性的变化,并把变化应用到

View中,也可以在onBindView中处理其他属性变化。


Preference的Enable属性

setEnable

/**
     * Sets whether this Preference is enabled. If disabled, it will
     * not handle clicks.
     * 
     * @param enabled Set true to enable it.
     */
    public void setEnabled(boolean enabled) {
        if (mEnabled != enabled) {
            mEnabled = enabled;

            // Enabled state can change dependent preferences' states, so notify
            notifyDependencyChange(shouldDisableDependents());

            notifyChanged();
        }
    }
看上面的 Preference#onBindView()方法,要将Enable属性和View关联起来,代码如下:

if (mShouldDisableView) {
            setEnabledStateOnViews(view, isEnabled());
        }
就是还需要mShouldDisableView属性为true才行。而且setEnabledStateOnViews()方法是private,

不调用父类onBindView,根本没有实现Enable属性对View生效。而isEnable()方法,也不只是考虑Enable

属性,其代码如下:

boolean isEnable()

/**
     * Checks whether this Preference should be enabled in the list.
     * 
     * @return True if this Preference is enabled, false otherwise.
     */
    public boolean isEnabled() {
        return mEnabled && mDependencyMet && mParentDependencyMet;
    }
还考虑了该Preference及父容器的dependency属性。



<think>好的,我现在需要详细解释Android中PreferencePreferenceScreen的用法。首先,我得回忆一下这两个类的基本作用,它们都属于Android的偏好设置框架,用于构建设置界面。 Preference应该是单个设置的基类,比如CheckBoxPreference、EditTextPreference这些具体子类,每个都代表一种类型的设置项。而PreferenceScreen可能是一个容器,用来组织多个Preference,可能是根布局,或者嵌套在里面的屏幕。 接下来,我得考虑如何组织内容。用户需要详细的使用方法,所以应该分步骤讲解,包括如何创建XML文件,如何添加各种Preference,以及PreferenceScreen的嵌套使用。可能还需要提到监听事件和动态添加Preference的方法。 记得系统级指令里提到,行内公式用$...$,独立公式用$$...$$,但这里可能不太涉及数学公式,不过代码块需要正确展示。另外用中文回答,结构清晰,分点说明。 需要确认是否正确区分PreferencePreferenceScreen的关系。例如,PreferenceScreen可以作为根元素,也可以包含其他PreferenceScreen来创建分层结构。每个Preference的XML属性可能有所不同,比如key、title、summary等。 可能还要提到在Activity中使用PreferenceFragmentCompat,因为现在推荐使用AndroidX库,特别是支持库中的组件。需要说明如何继承并加载XML资源。 另外,动态添加的部分,比如在代码中通过findPreference获取PreferenceScreen对象,然后使用addPreference方法动态添加。这里需要注意正确的方法调用和可能的错误处理。 监听器方面,比如OnPreferenceChangeListener和OnPreferenceClickListener,要说明如何设置和处理回调。 可能还需要提到一些常用的子类,如ListPreferenceSwitchPreference等,并给出简单示例。 最后,要确保所有代码示例的语法正确,符合LaTeX要求,但这里的代码是XML和Java/Kotlin,所以应该用代码块包裹,而不是LaTeX公式。不过系统指令可能希望所有技术内容正确,比如代码缩进和标签闭合。 检查是否有遗漏点,比如样式定制、存储机制(SharedPreferences)的关联,或者是否需要提到新的Jetpack Preference库的用法,因为旧的方法可能已经过时。比如,现在推荐使用PreferenceFragmentCompat,并且需要依赖androidx.preference库。 总结步骤可能包括: 1. 添加依赖 2. 创建Preference XML文件 3. 创建PreferenceFragmentCompat子类 4. 在Activity中加载Fragment 5. 各种Preference的配置 6. PreferenceScreen的嵌套使用 7. 动态添加和事件监听 8. 数据存储和获取 需要注意避免错误,比如Preference的key与SharedPreferences中存储的键对应,以及正确设置默认值。 可能用户还希望知道如何读取和写入这些设置,所以简要提及SharedPreferences的获取方法,如PreferenceManager.getDefaultSharedPreferences(context)。 现在需要将这些内容组织成结构清晰的中文回答,分点说明,确保每个部分都准确无误,并符合用户的提问要求。</think>在 Android 开发中,`Preference` 和 `PreferenceScreen` 是用于构建**设置界面**的核心组件,基于 `SharedPreferences` 实现数据存储。以下是详细用法说明: --- ### 一、基础概念 1. **Preference** - 表示单个设置项,如开关、输入框等。 - 常用子类: - `CheckBoxPreference`(复选框) - `EditTextPreference`(输入框) - `ListPreference`(下拉列表) - `SwitchPreference`(开关) - `PreferenceCategory`(分组容器) 2. **PreferenceScreen** - 定义设置界面的根容器,支持嵌套其他 `PreferenceScreen` 实现分层跳转。 - 通过 XML 文件组织布局结构。 --- ### 二、XML 文件配置 1. **创建 XML 文件** 在 `res/xml/` 目录下新建 `preferences.xml`,示例如下: ```xml <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 分组 --> <PreferenceCategory android:title="通用设置"> <SwitchPreference android:key="notifications" android:title="启用通知" android:defaultValue="true" /> <EditTextPreference android:key="username" android:title="用户名" android:dialogTitle="输入用户名" /> </PreferenceCategory> <!-- 嵌套子屏幕 --> <PreferenceScreen android:key="advanced_settings" android:title="高级设置"> <CheckBoxPreference android:key="debug_mode" android:title="调试模式" /> </PreferenceScreen> </PreferenceScreen> ``` --- ### 三、代码中加载 Preference 1. **使用 PreferenceFragmentCompat** - 依赖库:`androidx.preference:preference-ktx:1.2.0`(Java 或 Kotlin) - 创建 Fragment 并加载 XML: ```kotlin class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) } } ``` 2. **在 Activity 中绑定 Fragment** ```kotlin class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportFragmentManager .beginTransaction() .replace(android.R.id.content, SettingsFragment()) .commit() } } ``` --- ### 四、Preference 事件监听 1. **点击事件** ```kotlin val pref = findPreference<Preference>("username") pref?.setOnPreferenceClickListener { // 处理点击逻辑 true } ``` 2. **值变更监听** ```kotlin val switchPref = findPreference<SwitchPreference>("notifications") switchPref?.setOnPreferenceChangeListener { _, newValue -> val isChecked = newValue as Boolean // 处理开关状态变化 true } ``` --- ### 五、动态添加 Preference 通过代码动态生成设置项: ```kotlin override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { val context = preferenceManager.context val screen = preferenceManager.createPreferenceScreen(context) // 动态添加 SwitchPreference val dynamicSwitch = SwitchPreference(context).apply { key = "dynamic_switch" title = "动态开关" } screen.addPreference(dynamicSwitch) preferenceScreen = screen } ``` --- ### 六、数据存取 通过 `SharedPreferences` 直接读写设置项: ```kotlin val prefs = PreferenceManager.getDefaultSharedPreferences(context) val username = prefs.getString("username", "default_user") prefs.edit().putBoolean("notifications", false).apply() ``` --- ### 七、最佳实践 1. **Key 命名**:使用常量定义 key,避免硬编码。 2. **默认值**:始终通过 `android:defaultValue` 或代码设置默认值。 3. **嵌套层级**:避免超过 2 层 `PreferenceScreen`,防止用户体验复杂化。 通过以上步骤,你可以高效构建符合 Material Design 规范的设置界面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值