自定义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属性。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值