阳光沙滩
让学习编程变得简单
Android如何自定义Preference呢?
发表于 2020-03-08    阅读次数 398

Android如何自定义Preference呢?

图片描述

先理解源码

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;
    }

这里面很简单,就是用 Inflater去加载mLayoutResId这个id的xml,这个mLayoutResId是什么呢? private int mLayoutResId = com.android.internal.R.layout.preference; 所以说,这个id是定的:这个xml的布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
  <!-- Copyright (C) 2011 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.
  -->

  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:minHeight="?android:attr/listPreferredItemHeightSmall"
      android:gravity="center_vertical"
      android:paddingStart="?android:attr/listPreferredItemPaddingStart"
      android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
      android:background="?android:attr/selectableItemBackground">

      <RelativeLayout
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_weight="1"
          android:paddingTop="16dip"
          android:paddingBottom="16dip">

          <TextView
              android:id="@+android:id/title"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:singleLine="true"
              android:textAppearance="@android:style/TextAppearance.Material.Subhead"
              android:textColor="?android:attr/textColorPrimary"
              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:visibility="gone"
              android:textAlignment="viewStart"
              android:textAppearance="@android:style/TextAppearance.Material.Body1"
              android:textColor="?android:attr/textColorSecondary"
              android:maxLines="10" />

      </RelativeLayout>

      <LinearLayout
          android:id="@android:id/widget_frame"
          android:layout_width="wrap_content"
          android:layout_height="match_parent"
          android:minWidth="58dip"
          android:gravity="end|center_vertical"
          android:orientation="vertical" />

  </LinearLayout>

我们可以看到,这个布局很简单:有一个title和一个sumarry

剩下就是一个widget_frame

而前面的java代码里,也就是onCreateView那里,还有一部分代码,就是把mWidgetLayoutResId这个xml布局加载到widget_frame里头。 那mWidgetLayoutResId是怎么来的呢?

只有两个地方,一个地方是设置它,通过一个方法: public void setWidgetLayoutResource(int widgetLayoutResId)

另娃一个则是从属性中获取,也就是我们的布局属性中可以直接设置。

而它对应的控件使用,就会在onBindView里头找到对应的控件,成员变量。

到这里,我们基本明白了每一个Preference是怎么实现默认布局的吧!

谈到了自定义Preference

有了上面的基础,我们就知道了怎么去自定义一个Preference了。

首先,我们继承自Preference,然后覆写onCreateView方法,反回我们自己的编写的布局,接着复写onBindView去找到相应的控件。然后呢就是暴露一些方法吧!

先不说理论了,直接就上吧! 第一步:我们编写一个类,去继承自己Preference

没有第二步了,刚才写代码的时候没忍住,小手一抖,把整个例子写完了!先看代码吧,后面再进行解释:

  /**
    * Create by TrillGates 2017/8/11
    */
    public class VolumeSettingPreference extends Preference implements View.OnClickListener {


        private TextView mTitle;
        private String mTitleText;
        private int mDefaultVolume;
        private String mSummaryText;
        private String mKey;
        private TextView mSummary;
        private Dialog mShowDialog;
        private SeekBar mSeekBar;

        public VolumeSettingPreference(Context context) {
            this(context, null);
        }

        public VolumeSettingPreference(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }


        public VolumeSettingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //这里可以定一些自定义的属性,这跟自定义View的套路是一样的,比如说获取到title之类的,默认的音量是多少
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.voice_style);
            mTitleText = typedArray.getString(R.styleable.voice_style_title);
            mDefaultVolume = typedArray.getIndex(R.styleable.voice_style_default_voice);
            mSummaryText = typedArray.getString(R.styleable.voice_style_summary);
            mKey = typedArray.getString(R.styleable.voice_style_key);
            //重要,把key给老爸
            super.setKey(mKey);
            typedArray.recycle();
        }


        @Override
        protected View onCreateView(ViewGroup parent) {
            //拿到Invalter
            View content = LayoutInflater.from(getContext()).inflate(R.layout.voice_setting_layout, null);
            return content;
        }

        @Override
        protected void onBindView(View view) {
            int saveValue = getSharedPreferences().getInt(mKey, 0);
            mTitle = (TextView) view.findViewById(R.id.title);
            mSummary = (TextView) view.findViewById(R.id.summary);
            mTitle.setText(mTitleText);
            if (saveValue != 0) {
                mSummary.setText("当前音量:" + saveValue);
            } else {
                mSummary.setText(mSummaryText);
            }
            //设置这个view的点击事件,这里的话,我们会弹出一个dialog,去设置音量值
            mShowDialog = new Dialog(getContext());
            View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_volume_set_layout, null);
            mShowDialog.setContentView(dialogView);
            mShowDialog.setTitle("音量设置");
            view.setOnClickListener(this);
            mSeekBar = (SeekBar) dialogView.findViewById(R.id.dialog_volume_value);
            TextView cancel = (TextView) dialogView.findViewById(R.id.dialog_cancel);
            TextView ok = (TextView) dialogView.findViewById(R.id.dialog_ok);
            mSeekBar.setProgress(saveValue);
            mSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
            cancel.setOnClickListener(onCancelListener);
            ok.setOnClickListener(onOkClickListener);
            //这里是设置seekBar的总梯度
            mSeekBar.setMax(10);
        }

        @Override
        public void onClick(View v) {
            //条目被点击了
            mShowDialog.show();
        }

        private View.OnClickListener onCancelListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mShowDialog.dismiss();
            }
        };

        private View.OnClickListener onOkClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击确定的时候,把数据获取到,显示出来,并且保存起来
                persistInt(mSeekBar.getProgress());
                //设置UI
                mSummary.setText("当前音量是:" + mSeekBar.getProgress());
                mShowDialog.dismiss();
            }
        };


        private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                showText("当前音量为:" + seekBar.getProgress());
            }
        };

        private Toast mToast = null;

        private void showText(String summaryText) {
            if (mToast == null) {
                mToast = Toast.makeText(getContext(), summaryText, Toast.LENGTH_SHORT);
            } else {
                mToast.setText(summaryText);
            }
            mToast.show();
        }
    }

附加的文件呢?嘻嘻!如下: 自定义Preference的xml布局文件

   <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
          android:id="@+id/title"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerVertical="true"
          android:padding="16dp"
          android:text="我是标题"
          android:textSize="20sp"/>

      <TextView
          android:id="@+id/summary"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_alignParentRight="true"
          android:layout_centerVertical="true"
          android:padding="16dp"
          android:text="summary"
          android:textSize="18sp"/>

  </RelativeLayout>

自定义属性文件:

  <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="voice_style">
            <attr name="default_voice" format="integer"/>
            <attr name="title" format="string"/>
            <attr name="summary" format="string"/>
            <attr name="key" format="string"/>
        </declare-styleable>
    </resources>

弹出的对话框布局文件:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="300dp"
        android:layout_height="150dp"
        android:background="#424242">


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="150dp">

            <TextView
                android:id="@+id/dialog_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="音量设置"
                android:textSize="16sp"/>

            <SeekBar
                android:id="@+id/dialog_volume_value"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"/>

            <TextView
                android:id="@+id/dialog_cancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:padding="20dp"
                android:text="CANCEL"
                android:textColor="@color/colorAccent"/>

            <TextView
                android:id="@+id/dialog_ok"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_alignParentRight="true"
                android:padding="20dp"
                android:text="OK"
                android:textColor="@color/colorAccent"/>

        </RelativeLayout>

    </RelativeLayout>

应该就这么多了吧! 接下来就是简单解释一下吧:

首先我们按着前面的分析,设置布局UI,然后处理事件,保存内容!

每一个Preference都会创建对应的SharePreference,它用于保存数据

注意的是要把key设置给老爸哦!

保存数据的方式是父类的方法persist类型,比如说保存Int类型的就用persistInt这个方法保存即可!

  <?xml version="1.0" encoding="utf-8"?>
    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                      xmlns:sob="http://schemas.android.com/apk/res-auto">
        <PreferenceCategory
            android:title="无线和网络">
            <CheckBoxPreference
                android:defaultValue="true"
                android:key="is_auto_connect"
                android:summary="选中则自动连接!"
                android:title="是否自动连接wifi"/>
            <SwitchPreference
                android:defaultValue="false"
                android:key="is_wifi_open"
                android:summary="滑动打开wifi"
                android:title="Wifi"/>
            <EditTextPreference
                android:dialogTitle="请输入wifi密码"
                android:key="wifi_password"
                android:summary="点击输入密码"
                android:title="Wifi密码"/>
        </PreferenceCategory>
        <PreferenceCategory
            android:title="区域和爱好">
            <ListPreference
                android:entries="@array/countries"
                android:entryValues="@array/countries"
                android:key="country"
                android:summary="点击选择国家"
                android:title="国家"/>
            <MultiSelectListPreference
                android:entries="@array/balls"
                android:entryValues="@array/balls"
                android:key="hobbies"
                android:summary="点击选择你喜欢的球类"
                android:title="爱好"/>
        </PreferenceCategory>
        <PreferenceCategory
            android:title="其他">
            <PreferenceScreen
                android:summary="点击跳转到另外一个屏幕去看关于"
                android:title="开发关模式">
                <SwitchPreference
                    android:defaultValue="false"
                    android:key="is_development_mod"
                    android:title="打开开发者模式"/>
            </PreferenceScreen>

            <com.sunofbeaches.preferencedemo.view.VolumeSettingPreference
                sob:key="volume_value"
                sob:summary="点击设置音量"
                sob:title="音量设置"/>

        </PreferenceCategory>
    </PreferenceScreen>

最后面这个就是我们自己编写的啦!

<com.sunofbeaches.preferencedemo.view.VolumeSettingPreference
                sob:key="volume_value"
                sob:summary="点击设置音量"
                sob:title="音量设置"/>

运行结果

图片描述

已经过时了

现在已经过时了,哈哈!这篇文章在2017年的时候写的,现在还是记录下来吧,当然啦,还是可以用的嘛。