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属性。