SeekBarPreference.java revision 19798292948bce86c7300145d706cd42a7ac43c6
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.support.v4.content.res.TypedArrayUtils;
24import android.support.v7.preference.PreferenceViewHolder;
25import android.text.TextUtils;
26import android.util.AttributeSet;
27import android.view.KeyEvent;
28import android.view.View;
29import android.view.accessibility.AccessibilityNodeInfo;
30import android.widget.SeekBar;
31import android.widget.SeekBar.OnSeekBarChangeListener;
32
33import com.android.settings.widget.DefaultIndicatorSeekBar;
34import com.android.settingslib.RestrictedPreference;
35
36/**
37 * Based on android.preference.SeekBarPreference, but uses support preference as base.
38 */
39public class SeekBarPreference extends RestrictedPreference
40        implements OnSeekBarChangeListener, View.OnKeyListener {
41
42    private int mProgress;
43    private int mMax;
44    private int mMin;
45    private boolean mTrackingTouch;
46
47    private boolean mContinuousUpdates;
48    private int mDefaultProgress = -1;
49
50    private SeekBar mSeekBar;
51    private boolean mShouldBlink;
52    private int mAccessibilityRangeInfoType = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT;
53    private CharSequence mSeekBarContentDescription;
54
55    public SeekBarPreference(
56            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
57        super(context, attrs, defStyleAttr, defStyleRes);
58
59        TypedArray a = context.obtainStyledAttributes(
60                attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes);
61        setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax));
62        setMin(a.getInt(com.android.internal.R.styleable.ProgressBar_min, mMin));
63        a.recycle();
64
65        a = context.obtainStyledAttributes(attrs,
66                com.android.internal.R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
67        final int layoutResId = a.getResourceId(
68                com.android.internal.R.styleable.SeekBarPreference_layout,
69                com.android.internal.R.layout.preference_widget_seekbar);
70        a.recycle();
71
72        setLayoutResource(layoutResId);
73    }
74
75    public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
76        this(context, attrs, defStyleAttr, 0);
77    }
78
79    public SeekBarPreference(Context context, AttributeSet attrs) {
80        this(context, attrs, TypedArrayUtils.getAttr(context,
81                        android.support.v7.preference.R.attr.seekBarPreferenceStyle,
82                        com.android.internal.R.attr.seekBarPreferenceStyle));
83    }
84
85    public SeekBarPreference(Context context) {
86        this(context, null);
87    }
88
89    public void setShouldBlink(boolean shouldBlink) {
90        mShouldBlink = shouldBlink;
91        notifyChanged();
92    }
93
94    @Override
95    public void onBindViewHolder(PreferenceViewHolder view) {
96        super.onBindViewHolder(view);
97        view.itemView.setOnKeyListener(this);
98        mSeekBar = (SeekBar) view.findViewById(
99                com.android.internal.R.id.seekbar);
100        mSeekBar.setOnSeekBarChangeListener(this);
101        mSeekBar.setMax(mMax);
102        mSeekBar.setMin(mMin);
103        mSeekBar.setProgress(mProgress);
104        mSeekBar.setEnabled(isEnabled());
105        final CharSequence title = getTitle();
106        if (!TextUtils.isEmpty(mSeekBarContentDescription)) {
107            mSeekBar.setContentDescription(mSeekBarContentDescription);
108        } else if (!TextUtils.isEmpty(title)) {
109            mSeekBar.setContentDescription(title);
110        }
111        if (mSeekBar instanceof DefaultIndicatorSeekBar) {
112            ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress);
113        }
114        if (mShouldBlink) {
115            View v = view.itemView;
116            v.post(() -> {
117                if (v.getBackground() != null) {
118                    final int centerX = v.getWidth() / 2;
119                    final int centerY = v.getHeight() / 2;
120                    v.getBackground().setHotspot(centerX, centerY);
121                }
122                v.setPressed(true);
123                v.setPressed(false);
124                mShouldBlink = false;
125            });
126        }
127        mSeekBar.setAccessibilityDelegate(new View.AccessibilityDelegate() {
128            @Override
129            public void onInitializeAccessibilityNodeInfo(View view, AccessibilityNodeInfo info) {
130                super.onInitializeAccessibilityNodeInfo(view, info);
131                // Update the range info with the correct type
132                AccessibilityNodeInfo.RangeInfo rangeInfo = info.getRangeInfo();
133                if (rangeInfo != null) {
134                    info.setRangeInfo(AccessibilityNodeInfo.RangeInfo.obtain(
135                                    mAccessibilityRangeInfoType, rangeInfo.getMin(),
136                                    rangeInfo.getMax(), rangeInfo.getCurrent()));
137                }
138            }
139        });
140    }
141
142    @Override
143    public CharSequence getSummary() {
144        return null;
145    }
146
147    @Override
148    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
149        setProgress(restoreValue ? getPersistedInt(mProgress)
150                : (Integer) defaultValue);
151    }
152
153    @Override
154    protected Object onGetDefaultValue(TypedArray a, int index) {
155        return a.getInt(index, 0);
156    }
157
158    @Override
159    public boolean onKey(View v, int keyCode, KeyEvent event) {
160        if (event.getAction() != KeyEvent.ACTION_DOWN) {
161            return false;
162        }
163
164        SeekBar seekBar = (SeekBar) v.findViewById(com.android.internal.R.id.seekbar);
165        if (seekBar == null) {
166            return false;
167        }
168        return seekBar.onKeyDown(keyCode, event);
169    }
170
171    public void setMax(int max) {
172        if (max != mMax) {
173            mMax = max;
174            notifyChanged();
175        }
176    }
177
178    public void setMin(int min) {
179        if (min != mMin) {
180            mMin = min;
181            notifyChanged();
182        }
183    }
184
185    public int getMax() {
186        return mMax;
187    }
188
189    public int getMin() {
190        return mMin;
191    }
192
193    public void setProgress(int progress) {
194        setProgress(progress, true);
195    }
196
197    /**
198     * Sets the progress point to draw a single tick mark representing a default value.
199     */
200    public void setDefaultProgress(int defaultProgress) {
201        if (mDefaultProgress != defaultProgress) {
202            mDefaultProgress = defaultProgress;
203            if (mSeekBar instanceof DefaultIndicatorSeekBar) {
204                ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress);
205            }
206        }
207    }
208
209    /**
210     * When {@code continuousUpdates} is true, update the persisted setting immediately as the thumb
211     * is dragged along the SeekBar. Otherwise, only update the value of the setting when the thumb
212     * is dropped.
213     */
214    public void setContinuousUpdates(boolean continuousUpdates) {
215        mContinuousUpdates = continuousUpdates;
216    }
217
218    private void setProgress(int progress, boolean notifyChanged) {
219        if (progress > mMax) {
220            progress = mMax;
221        }
222        if (progress < mMin) {
223            progress = mMin;
224        }
225        if (progress != mProgress) {
226            mProgress = progress;
227            persistInt(progress);
228            if (notifyChanged) {
229                notifyChanged();
230            }
231        }
232    }
233
234    public int getProgress() {
235        return mProgress;
236    }
237
238    /**
239     * Persist the seekBar's progress value if callChangeListener
240     * returns true, otherwise set the seekBar's progress to the stored value
241     */
242    void syncProgress(SeekBar seekBar) {
243        int progress = seekBar.getProgress();
244        if (progress != mProgress) {
245            if (callChangeListener(progress)) {
246                setProgress(progress, false);
247            } else {
248                seekBar.setProgress(mProgress);
249            }
250        }
251    }
252
253    @Override
254    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
255        if (fromUser && (mContinuousUpdates || !mTrackingTouch)) {
256            syncProgress(seekBar);
257        }
258    }
259
260    @Override
261    public void onStartTrackingTouch(SeekBar seekBar) {
262        mTrackingTouch = true;
263    }
264
265    @Override
266    public void onStopTrackingTouch(SeekBar seekBar) {
267        mTrackingTouch = false;
268        if (seekBar.getProgress() != mProgress) {
269            syncProgress(seekBar);
270        }
271    }
272
273    /**
274     * Specify the type of range this seek bar represents.
275     *
276     * @param rangeInfoType The type of range to be shared with accessibility
277     *
278     * @see android.view.accessibility.AccessibilityNodeInfo.RangeInfo
279     */
280    public void setAccessibilityRangeInfoType(int rangeInfoType) {
281        mAccessibilityRangeInfoType = rangeInfoType;
282    }
283
284    public void setSeekBarContentDescription(CharSequence contentDescription) {
285        mSeekBarContentDescription = contentDescription;
286        if (mSeekBar != null) {
287            mSeekBar.setContentDescription(contentDescription);
288        }
289    }
290
291    @Override
292    protected Parcelable onSaveInstanceState() {
293        /*
294         * Suppose a client uses this preference type without persisting. We
295         * must save the instance state so it is able to, for example, survive
296         * orientation changes.
297         */
298
299        final Parcelable superState = super.onSaveInstanceState();
300        if (isPersistent()) {
301            // No need to save instance state since it's persistent
302            return superState;
303        }
304
305        // Save the instance state
306        final SavedState myState = new SavedState(superState);
307        myState.progress = mProgress;
308        myState.max = mMax;
309        myState.min = mMin;
310        return myState;
311    }
312
313    @Override
314    protected void onRestoreInstanceState(Parcelable state) {
315        if (!state.getClass().equals(SavedState.class)) {
316            // Didn't save state for us in onSaveInstanceState
317            super.onRestoreInstanceState(state);
318            return;
319        }
320
321        // Restore the instance state
322        SavedState myState = (SavedState) state;
323        super.onRestoreInstanceState(myState.getSuperState());
324        mProgress = myState.progress;
325        mMax = myState.max;
326        mMin = myState.min;
327        notifyChanged();
328    }
329
330    /**
331     * SavedState, a subclass of {@link BaseSavedState}, will store the state
332     * of MyPreference, a subclass of Preference.
333     * <p>
334     * It is important to always call through to super methods.
335     */
336    private static class SavedState extends BaseSavedState {
337        int progress;
338        int max;
339        int min;
340
341        public SavedState(Parcel source) {
342            super(source);
343
344            // Restore the click counter
345            progress = source.readInt();
346            max = source.readInt();
347            min = source.readInt();
348        }
349
350        @Override
351        public void writeToParcel(Parcel dest, int flags) {
352            super.writeToParcel(dest, flags);
353
354            // Save the click counter
355            dest.writeInt(progress);
356            dest.writeInt(max);
357            dest.writeInt(min);
358        }
359
360        public SavedState(Parcelable superState) {
361            super(superState);
362        }
363
364        @SuppressWarnings("unused")
365        public static final Parcelable.Creator<SavedState> CREATOR =
366                new Parcelable.Creator<SavedState>() {
367            public SavedState createFromParcel(Parcel in) {
368                return new SavedState(in);
369            }
370
371            public SavedState[] newArray(int size) {
372                return new SavedState[size];
373            }
374        };
375    }
376}
377