设置页面效果图如下:都系统的,没做修改,所以有点丑,后边再修改
先看下PreferenceActivity系统源码,单面板还是多面板,onIsMultiPane()这个平板的话一般都是true,普通手机应该是false了。
boolean hidingHeaders = onIsHidingHeaders();
mSinglePane = hidingHeaders || !onIsMultiPane();
/**
* Returns true if this activity is showing multiple panes -- the headers
* and a preference fragment.
*/
public boolean isMultiPane() {
return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
}
/**
* Called to determine if the activity should run in multi-pane mode.
* The default implementation returns true if the screen is large
* enough.
*/
public boolean onIsMultiPane() {
boolean preferMultiPane = getResources().getBoolean(
com.android.internal.R.bool.preferences_prefer_dual_pane);
return preferMultiPane;
}
/**
* Called to determine whether the header list should be hidden.
* The default implementation returns the
* value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
* This is set to false, for example, when the activity is being re-launched
* to show a particular preference activity.
*/
public boolean onIsHidingHeaders() {
return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
}
左边header条目的点击事件处理,也是区分单面板和多面板,单面板的情况下,右边是隐藏的
/**
* Start a new fragment containing a preference panel. If the preferences
* are being displayed in multi-pane mode, the given fragment class will
* be instantiated and placed in the appropriate pane. If running in
* single-pane mode, a new activity will be launched in which to show the
* fragment.
*
* @param fragmentClass Full name of the class implementing the fragment.
* @param args Any desired arguments to supply to the fragment.
* @param titleRes Optional resource identifier of the title of this
* fragment.
* @param titleText Optional text of the title of this fragment.
* @param resultTo Optional fragment that result data should be sent to.
* If non-null, resultTo.onActivityResult() will be called when this
* preference panel is done. The launched panel must use
* {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
* @param resultRequestCode If resultTo is non-null, this is the caller's
* request code to be received with the result.
*/
public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
CharSequence titleText, Fragment resultTo, int resultRequestCode) {
if (mSinglePane) {
startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
} else {
Fragment f = Fragment.instantiate(this, fragmentClass, args);
if (resultTo != null) {
f.setTargetFragment(resultTo, resultRequestCode);
}
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(com.android.internal.R.id.prefs, f);
if (titleRes != 0) {
transaction.setBreadCrumbTitle(titleRes);
} else if (titleText != null) {
transaction.setBreadCrumbTitle(titleText);
}
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
transaction.commitAllowingStateLoss();
}
}
我们看下它默认加载的布局文件,在oncreate里可以找到com.android.internal.R.layout.preference_list_content。
知道了布局文件的名字,然后我们就去sdk下找,如下,至于platform下随便选个应该都有,我这里就选了个19的,路径如下sdk\platforms\android-19\data\res\layout ,结构如下
最下边那个相对布局3个按钮看源码都是finish页面的,外加一个setResult而已,不研究,上边布局就是我们的用的,左边Linearlayout 就是header了,里边有个listview和一个footer,右边LinearLayout里也有2个,上边include那个叫breadcrumbs就是平时手机文件夹上边那个可以后退的导航的玩意,下边那个fragment就是和左边header的item对应显示的内容了。
布局了解了,继续看下,数据咋加载的,还是在onCreate里,可以看到我们只需要给onBuildHeaders这个方法里给mHeaders集合赋值就完事拉。其他的基类都做完了。
onBuildHeaders(mHeaders);
中间省略N行代码
else if (mHeaders.size() > 0) {
setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
mPreferenceHeaderRemoveEmptyIcon));
if (!mSinglePane) {
// Multi-pane.
getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
if (mCurHeader != null) {
setSelectedHeader(mCurHeader);
}
mPrefsContainer.setVisibility(View.VISIBLE);
}
} else {
// If there are no headers, we are in the old "just show a screen
// of preferences" mode.
setContentView(com.android.internal.R.layout.preference_list_content_single);
mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
mPreferenceManager.setOnPreferenceTreeClickListener(this);
}
上边还可以发现如果没有header的话,默认加载preference_list_content_single这个布局,这个布局和上边的那个布局区别就是少了header那个线性布局,其他都一样。
所以要我们的activity只要继承PreferenceActivity,完事重写下onBuildHeaders方法,给target赋值即可,
下边的就是调用loadHeadersFromResource方法通过xml来赋值,然后我们动态添加了一个Header,
赋值fragment的话,到时候点击就是切换右侧的fragment显示,赋值intent自然就是跳到新页面拉。
header.fragment后边跟的是你要跳转的Fragment的完整路径
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.setting_headers, target);
Header header=new Header();
header.title="动态添加的";
header.summary="看需求要不要summary决定";
header.iconRes=R.drawable.adp_settingsactivity_information_icon;
header.fragment="preference.FragmentGeneral";
header.fragmentArguments=new Bundle();
// header.intent=new Intent(this,ActivityBrightness.class);
target.add(header);
super.onBuildHeaders(target);
}
xml文件如下:想显示fragment就写android:fragment属性,想跳页面就加上Intent属性。
<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
<header
android:fragment="preference.FragmentGeneral"
android:icon="@drawable/adp_settingsactivity_general_icon"
android:summary="normal set"
android:title="General" />
<header
android:icon="@drawable/adp_settingsactivity_network_icon"
android:title="Network" >
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.charlie.demo0108.simple.ActivityWifiSetting"
android:targetPackage="com.charlie.demo0108" />
</header>
<header
android:fragment="preference.FragmentGeneral"
android:icon="@drawable/adp_settingsactivity_navigation_icon"
android:title="Navigation+Maps" />
<header
android:fragment="preference.FragmentGeneral"
android:icon="@drawable/adp_settingsactivity_safety_warning_system_icon"
android:title="Safety+Security" />
<header
android:fragment="preference.FragmentGeneral"
android:icon="@drawable/adp_settingsactivity_information_icon"
android:title="information" />
<header
android:icon="@drawable/adp_settingsactivity_register_icon"
android:title="Admin" >
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.charlie.demo0108.simple.ActivityWifiSetting"
android:targetPackage="com.charlie.demo0108" />
</header>
</preference-headers>
至于监听点击事件做些截断处理,可以重写如下方法
@Override
public void onHeaderClick(Header header, int position) {
System.err.println(getClass().getSimpleName() + " onHeaderClick=================" + header.title);
super.onHeaderClick(header, position);
}
对了,重要的一点,如果你app的target版本大于19的话,必须重写如下方法返回true,原因点下super进到源码上边有注释
@Override
protected boolean isValidFragment(String fragmentName) {
return true;
}
源码如下:
/**
* Subclasses should override this method and verify that the given fragment is a valid type
* to be attached to this activity. The default implementation returns <code>true</code> for
* apps built for <code>android:targetSdkVersion</code> older than
* {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
* @param fragmentName the class name of the Fragment about to be attached to this activity.
* @return true if the fragment class name is valid for this Activity and false otherwise.
*/
protected boolean isValidFragment(String fragmentName) {
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
throw new RuntimeException(
"Subclasses of PreferenceActivity must override isValidFragment(String)"
+ " to verify that the Fragment class is valid! " + this.getClass().getName()
+ " has not checked if fragment " + fragmentName + " is valid.");
} else {
return true;
}
}
嗯,好像还少个fragment,下边就说下fragment咋写,也简单,我们都通过xml来弄
fragment继承PreferenceFragment,然后如下加载xml就完事了,更简单。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences_test);
}
下边是xml文件,随便写了几个简单的。具体的用法后边再说,或者自行百度哈。
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<Preference
android:key="key_general"
android:title="SETTINGS_GENERAL_BRIGHTNESS"
android:dialogTitle="SETTINGS_GENERAL_BRIGHTNESS"
android:layout="@layout/preference_arrow" >
<!-- Start Intent from application -->
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.charlie.demo0108.simple.ActivityBrightness"
android:targetPackage="com.charlie.demo0108" />
</Preference>
<ListPreference
android:defaultValue="30"
android:entries="@array/screen_time_keep"
android:entryValues="@array/screen_time_keep_value"
android:key="key_auto_power"
android:title="dialog_timeout"
android:layout="@layout/preference" />
<CheckBoxPreference
android:title="夜间模式"
android:key="key_night_mode"/>
<SwitchPreference
android:switchTextOn="仅wifi下载"
android:switchTextOff=""
android:key="key_wifi_only"
android:title="下载限制"
/>
<CheckBoxPreference
android:title="不保留活动"
android:summary="用户离开后立即清除每个活动"
android:layout="@layout/preference_checkbox"
android:widgetLayout="@layout/widget_checkbox"
android:key="key_clear"/>
</PreferenceScreen>
至于都有哪些preference,可以看下下图的源码
上边的xml文件里,可以看到有个preference里自定义了layout,那么我们先看下系统默认的布局文件
找到sdk\sources\android-19\android\preference目录下的Preference.java这类,可以看到下行代码,也就是它的默认使用的布局
private int mLayoutResId = com.android.internal.R.layout.preference;
完事我们去sdk\platforms\android-19\data\res\layout下找到这个布局文件,如下
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Layout for a Preference in a PreferenceActivity. The
Preference is able to place a specific widget for its particular
type in the "widget_frame" layout. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:background="?android:attr/selectableItemBackground" >
<ImageView
android:id="@+android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dip"
android:layout_marginEnd="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: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: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>
可以看到就是一个icon,title,summary,完事右边还有个widget_frame。前3个的值就对应我们在xml里设置的
再看下我们开头给的图,我们的第一个preference是带个箭头的,其实就是复制上边的布局,然后后边添加了个imageview而已。
或者也可以和CheckBoxPreference一样,添加一个 android:widgetLayout="@layout/widget_checkbox"属性把箭头放到这个布局里,
看下Preference的源码,可以看到如果设置了widgetLayout,它就会自动添加进去的。
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
final ViewGroup widgetFrame = (ViewGroup) layout
.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
if (mWidgetLayoutResId != 0) {
layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
} else {
widgetFrame.setVisibility(View.GONE);
}
}
return layout;
}
继续研究下CheckBoxPreference它的布局是哪里设置了,半天没找到,根本没看到它设置默认值啊,后来发现它构造方法里传了个默认的style,那么布局肯定在这里了。
public CheckBoxPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
}
attrs.xml里有如下代码
<!-- Default style for CheckBoxPreference. -->
<attr name="checkBoxPreferenceStyle" format="reference" />
在如下资源文件下找sdk\platforms\android-19\data\res\values styles.xml文件
可以看到它默认设置了android:widgetLayout这个值了,里边就是个checkbox控件。
<!-- Preference Styles -->
<style name="Preference">
<item name="android:layout">@android:layout/preference</item>
</style>
<style name="PreferenceFragment">
<item name="android:paddingStart">0dp</item>
<item name="android:paddingEnd">0dp</item>
</style>
<style name="Preference.Information">
<item name="android:layout">@android:layout/preference_information</item>
<item name="android:enabled">false</item>
<item name="android:shouldDisableView">false</item>
</style>
<style name="Preference.Category">
<item name="android:layout">@android:layout/preference_category</item>
<!-- The title should not dim if the category is disabled, instead only the preference children should dim. -->
<item name="android:shouldDisableView">false</item>
<item name="android:selectable">false</item>
</style>
<style name="Preference.CheckBoxPreference">
<item name="android:widgetLayout">@android:layout/preference_widget_checkbox</item>
</style>
<style name="Preference.SwitchPreference">
<item name="android:widgetLayout">@android:layout/preference_widget_switch</item>
<item name="android:switchTextOn">@android:string/capital_on</item>
<item name="android:switchTextOff">@android:string/capital_off</item>
</style>
为啥format=reference可以关联到上边的style?
看第二篇可以知道,原来中间还有一个theme.xml的文件,里边有如下的代码做中间过渡就理解了
<style name="Theme">
<item name="preferenceFragmentStyle">@style/PreferenceFragment</item>
<item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item>
额外知识
自定义Preference
比如系统的ListPreference默认的布局上边有,左边一个icon,中间是title和summary,如果我们不想要这种布局,想加几个别的view进去,咋设置数据?
xml里使用自定义的布局
android:widgetLayout="@layout/preferen_layout"
布局如下,
<LinearLayout android:id="@+id/zzz"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:src="@drawable/ic_trash_d"
android:layout_width="40dp"
android:layout_height="40dp"/>
<TextView
android:id="@+id/tv_value"
android:text="xxx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:src="@drawable/ic_narrow_right_l"
android:layout_width="40dp"
android:layout_height="40dp"/>
</LinearLayout>
代码,继承ListPreference,如下,在onBindView里设置数据
public class MyListPreference extends ListPreference {
public MyListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListPreference(Context context) {
super(context);
}
protected void onBindView(View view) {
((TextView)view.findViewById(R.id.tv_value)).setText(getEntry());
super.onBindView(view);
}
}
看下代码,widgetLayout的布局最终是添加到原生预留的右边那个容器里的,原始布局上边代码有
如果你要替换整个布局,那就用属性layout,然后自己处理数据的设置,在onBindView里写