自定义SpinnerPreference

 我们知道Android提供了CheckBoxPreference, EditTextPreference, ListPreference, MultiSelectListPreference, SwitchPreference等控件。但这些控件不能满足日常有些需求,比如客户要这种效果。
image

这种效果,当然我们可以用Textview和Spinner嵌套在LinearLayout中实现,但可以扩展性不好,如果有多项话,Layout文件会显得很臃肿。所以我们会想到采用preference来实现。不过在Android提供的Preference中并没有类似于效果图中的这种带有Spinner的,于是就有了这篇博客。

一,整体方法

实现带Spinner的Preference我们分为以下几个步骤:

  1. 将Spinner作为widget加进preference。
  2. 定义Spinner对应的显示的数据以及显示数据的对应值等属性,这个类似于ListPreference的entries和entrieValues。
  3. 设定Spinner数据改变的接口
  4. 在xml文件中引用

二,具体实现

  我们将要实现的preference定义为SpinnerPreference:

  • 第一步,我们在SpinnerPreference的构造方法通过setWidgetLayoutResource(R.layout.custom_spinner_preference)方法将Spinner加入到preference中。custom_spinner_preferenced定义如下:
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.AppCompatSpinner
xmlns:android="http://schemas.android.com/apk/res/android"
android:focusableInTouchMode="true"
android:id="@+id/preference_spinner"
android:popupBackground="@drawable/shape_bg_spinner"
android:background="@drawable/bg_spinner"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
  • 第二步,我们要定义spinner中的数据来源,我们参照ListPreference的方式,在atrrs.xml中定义spinner显示的数据以及显示的显示的数据对应的值:

    1
    2
    3
    4
    <declare-styleable name="SpinnerPreference">
    <attr name="entries" format="reference"/>
    <attr name="entryValues" format="reference"/>
    </declare-styleable>
  • 第三步,设定spinner数据改变接口。这个主要是在spinner设置的 的OnItemSelectedListener中实现。具体实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    CharSequence value = mEntryValues[position];
    if (!value.equals(mCurrentValue)){
    mOnChangeListener.onPreferenceChange(SpinnerPreference.this,value);
    mCurrentValue = value;
    SpinnerPreference.this.persistInt(position);
    }
    }
    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
    });

SpinnerPreference 对外提供的数据改变接口

1
2
3
public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
mOnChangeListener = onPreferenceChangeListener;
}

  • 第四步,在xml中引用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.szxen.carsetting">
    <CheckBoxPreference
    android:key="key_radar_switch_to_reverse"
    android:layout="@layout/custom_preference"
    android:title="@string/radar_switch_2_reverse"
    android:widgetLayout="@layout/custom_preference_widget_checkbox" />
    <com.szxen.carsetting.widget.SpinnerPreference
    app:entries="@array/camera_entries"
    app:entryValues="@array/camera_entries_value"
    android:key="key_camera_front"
    android:layout="@layout/custom_preference"
    android:title="@string/camera_front" />
    <com.szxen.carsetting.widget.SpinnerPreference
    app:entries="@array/back_camera_entries"
    app:entryValues="@array/back_camera_entries_value"
    android:key="key_camera_back"
    android:layout="@layout/custom_preference"
    android:title="@string/camera_back" />
    <com.szxen.carsetting.widget.SpinnerPreference
    app:entries="@array/camera_entries"
    app:entryValues="@array/camera_entries_value"
    android:key="key_camera_left"
    android:layout="@layout/custom_preference"
    android:title="@string/camera_left" />
    <com.szxen.carsetting.widget.SpinnerPreference
    app:entries="@array/camera_entries"
    app:entryValues="@array/camera_entries_value"
    android:key="key_camera_right"
    android:layout="@layout/custom_preference"
    android:title="@string/camera_right" />
    </PreferenceScreen>

说明:custom_preference为了改变系统preference默认layout的文件,内容和系统的相差无几,主要是布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?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="@dimen/preference_item_width"
android:layout_height="@dimen/preference_item_height"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/scrollbarSize"
android:background="@drawable/selector_list" >
<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"
style="@style/preferenceTitleStyle"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@android:id/title"
android:layout_alignTop="@android:id/title"
android:layout_alignParentRight="true"
android:gravity="right"
style="@style/preferenceSummaryStyle"
android:maxLines="4" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+android:id/widget_frame"
android:layout_width="@dimen/preference_widget_width"
android:layout_height="match_parent"
android:gravity="left|center_vertical"
android:orientation="vertical" />
</LinearLayout>

最后,附上SpinnerPreference的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.szxen.carsetting.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.Preference;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Spinner;
import com.szxen.carsetting.R;
import com.szxen.carsetting.utils.PreferenceUtils;
/**
* Created by zhuzp on 2016/9/6.
*/
public class SpinnerPreference extends Preference implements View.OnClickListener,View.OnKeyListener{
private static final String TAG = "SpinnerPreference";
private Context mContext;
private CharSequence[] mEntries;
private CharSequence[] mEntryValues;
private CharSequence mCurrentValue = null;
private Preference.OnPreferenceChangeListener mOnChangeListener;
private Spinner mSpinner;
private int mDefaultPosition = 0;
public SpinnerPreference(Context context) {
this(context, null);
}
public SpinnerPreference(Context context, AttributeSet attrs) {
super(context,attrs);
setWidgetLayoutResource(R.layout.custom_spinner_preference);
init(context,attrs);
}
private void init(Context context, AttributeSet attrs){
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SpinnerPreference);
mEntries = a.getTextArray(R.styleable.SpinnerPreference_entries);
mEntryValues = a.getTextArray(R.styleable.SpinnerPreference_entryValues);
a.recycle();
mCurrentValue = mEntryValues[getPersistedInt(mDefaultPosition)];
}
@Override
protected View onCreateView(ViewGroup parent) {
View view = super.onCreateView(parent);
view.requestFocus();
view.setOnClickListener(this);
//处理按键事件
view.setOnKeyListener(this);
return view;
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
mSpinner = (Spinner) view.findViewById(R.id.preference_spinner);
mSpinner.setAdapter(new MySpinnerAdapter(mContext,mEntries));
mSpinner.setSelection(getPersistedInt(mDefaultPosition));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
CharSequence value = mEntryValues[position];
if (!value.equals(mCurrentValue)){
mOnChangeListener.onPreferenceChange(SpinnerPreference.this,value);
mCurrentValue = value;
SpinnerPreference.this.persistInt(position);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
mOnChangeListener = onPreferenceChangeListener;
}
@Override
public void onClick(View v) {
Log.d(TAG,"onClick.....");
mSpinner.performClick();
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode){
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
mSpinner.performClick();
return true;
}
return false;
}
}