构建 Android 自定义首选项 SeekBarPreference

在做设置(settings)界面时,首选项可由 Preference 类的特定子类表示,如 CheckBoxPreference、ListPreference、CheckBoxPreference 等几种常用的首选项。不过,需要实现控制条的首选项时,就没有内置 SeekBarPreference。还好,Android 可以构建自定义首选项,而且发现在  Android 的源代码中有这么一个 SeekBarPreference 类:

https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/android/preference/SeekBarPreference.java

或者https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/SeekBarPreference.java

通过参考 Android 源码,实现了一个满足自己需求 SeekBarPreference ,可以数据自动存储、设置默认的进度和最大进度、显示当前的进度和设置进度的单位。

首先定义一个 xml 布局文件来描述 SeekBar 的布局:

/res/layout/preference_widget_seekbar.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:minHeight="?android:attr/listPreferredItemHeight"
              android:orientation="vertical"
              android:gravity="center_vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="6dp"
        android:layout_marginBottom="6dp"
        android:orientation="vertical">

        <TextView android:id="@+android:id/title"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:textAppearance="?android:attr/textAppearanceMedium"
                  android:ellipsize="marquee"
                  android:fadingEdge="horizontal"
                  android:maxLines="1"/>

        <TextView android:id="@android:id/summary"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:textAppearance="?android:attr/textAppearanceSmall"
                  android:textColor="?android:attr/textColorSecondary"
                  android:maxLines="4" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center_vertical">
            <SeekBar
                android:id="@+id/seekbar"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"/>

            <TextView
                android:id="@+id/seekbar_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:textColor="?android:attr/textColorPrimary"
                tools:text="0"/>

            <TextView
                android:id="@+id/seekbar_unit"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:textColor="?android:attr/textColorSecondary"
                tools:text="Unit"/>
        </LinearLayout>

    </LinearLayout>
</LinearLayout>

说明:

<SeekBar
                android:id="@+id/seekbar"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"/>

中 layout_width=”0dp” 和 layout_weight=”1″ 是了为填满剩余空间。

在 /res/values/attrs.xml (如果没有此文件,新建一个) 中声明自定义的属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SeekBarPreference">
        <attr name="max" format="integer"/>
        <attr name="progress" format="integer"/>
        <attr name="unit" format="string"/>
    </declare-styleable>
</resources>

然后新建一个 SeekBarPreferences 类:

package com.icodechef.android.seekbar;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;

import com.icodechef.android.seekbar.R;

/**
 * Created by iCodechef on 2017/8/2.
 *
 * @link https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/android/preference/SeekBarPreference.java
 * @link https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/SeekBarPreference.java
 */
public class SeekBarPreference extends Preference
    implements SeekBar.OnSeekBarChangeListener{

    private static final int DEFAULT_MAX_VALUE = 100;

    private SeekBar mSeekBar;
    private TextView mValueView;

    private int mMax = DEFAULT_MAX_VALUE;
    private int mProgress;

    private String mUnit;

    public SeekBarPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekBarPreference);

        // 设置最大值
        setMax(a.getInt(R.styleable.SeekBarPreference_max, mMax));
        // 设置默认值
        setProgress(a.getInt(R.styleable.SeekBarPreference_progress, mProgress));

        setUnit(a.getString(R.styleable.SeekBarPreference_unit));
        a.recycle();

        // 设置 layout 资源 ID
        setLayoutResource(R.layout.preference_widget_seekbar);
    }

    @Override
    protected void onBindView(View view) {
        super.onBindView(view);

        mSeekBar = (SeekBar)view.findViewById(R.id.seekbar);
        mValueView = (TextView)view.findViewById(R.id.seekbar_value);

        mSeekBar.setMax(mMax);
        setProgress(mProgress);
        mSeekBar.setOnSeekBarChangeListener(this);
        mSeekBar.setEnabled(isEnabled());

        mValueView.setEnabled(isEnabled());
        mValueView.setText(String.valueOf(mProgress));

        if (mUnit != null && mUnit.length() > 0) {
            ((TextView)view.findViewById(R.id.seekbar_unit)).setText(mUnit);
        }
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        setProgress(restorePersistedValue ? getPersistedInt(mProgress) : (Integer)defaultValue);
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getInt(index, 0);
    }

    public void setMax(int max) {
        if (max != mMax) {
            mMax = max;
        }
    }

    private void setUnit(String unit) {
        mUnit = unit;
    }

    private void setProgress(int progress) {
        if (progress > mMax) {
            progress = mMax;
        } else if (progress < 0) {
            progress = 0;
        }

        mProgress = progress;

        if (mSeekBar != null) {
            mSeekBar.setProgress(progress);
        }

        persistInt(progress);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (callChangeListener(progress)) {
            if (fromUser) {
                setProgress(progress);
            }

            if (mValueView != null) {
                mValueView.setText(String.valueOf(progress));
            }
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }
}

说明:

1. 指定用户界面:

setLayoutResource(R.layout.preference_widget_seekbar);

2.初始化当前值:

onSetInitialValue 方法

系统将 Preference 添加到屏幕时,会调用 onSetInitialValue() 来通知您设置是否具有保留值。如果没有保留值,则此调用将为您提供默认值

3.提供默认值:

onGetDefaultValue 方法

4.保存设置的值:

可通过调用 Preference 类的一个 persist*() 方法(如 persistInt())随时保存该值。

 

下面是使用方法:

效果图如下:

Screenshot_2017-08-04-09-11-30-960_com.icodechef.android.seekbar

在 /res/xml/ 中新建 pref_settings.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.icodechef.android.seekbar.SeekBarPreference
        android:key="pref_person"
        android:title="人数"
        android:summary="这里是一个说明"
        app:max="100"
        app:progress="20"
        app:unit="人"/>

    <com.icodechef.android.seekbar.SeekBarPreference
        android:key="pref_time"
        android:title="时间"
        android:summary="这里是另外一个说明"
        app:max="10"
        app:progress="5"
        app:unit="分钟"/>

</PreferenceScreen>

MainActivity:

package com.icodechef.android.seekbar;

import android.app.FragmentTransaction;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();

        fragmentTransaction.replace(android.R.id.content, new SettingsFragment());
        fragmentTransaction.commit();
    }

    public static class SettingsFragment extends PreferenceFragment {
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_settings);
        }
    }
}

 

在 “构建 Android 自定义首选项 SeekBarPreference” 上有 2 条评论

  1. 为什么我的代码前面没有空格啊,都从每一行的开头开始,看起来一点都不专业

发表评论

电子邮件地址不会被公开。 必填项已用*标注