【一】PreferenceActivity和PreferenceFragment来写设置页面

设置页面效果图如下:都系统的,没做修改,所以有点丑,后边再修改


image.png

先看下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对应显示的内容了。


image.png

布局了解了,继续看下,数据咋加载的,还是在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,可以看下下图的源码


image.png

上边的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的布局最终是添加到原生预留的右边那个容器里的,原始布局上边代码有


image.png

如果你要替换整个布局,那就用属性layout,然后自己处理数据的设置,在onBindView里写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,570评论 25 707
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,088评论 2 44
  • 说好的,开始坚持每天写作的,但是今晚实在不知道写一些什么。 就想起每天在为别人写故事的我,是时候写一下自己的故事了...
    一凡SU阅读 177评论 0 0
  • 文/桃小夭 1 最近《三生三世十里桃花》大概成了电视剧里的NO1,更新的集数满足不了妹子仰慕天族太子汹涌澎湃的心,...
    桃小夭3199阅读 1,304评论 9 10
  • 亲爱的弟弟,你毕业了,也即将进入社会了。在此我决定说点什么。不全然是为你而说,只是找个话头。不必感到负担。 老姐我...
    Madeleine_Chow阅读 140评论 0 1