diff options
-rw-r--r-- | core/java/android/preference/Preference.java | 12 | ||||
-rw-r--r-- | core/java/android/preference/PreferenceFragment.java | 19 | ||||
-rw-r--r-- | core/java/android/preference/SeekBarDialogPreference.java | 67 | ||||
-rw-r--r-- | core/java/android/preference/SeekBarPreference.java | 229 | ||||
-rw-r--r-- | core/java/android/preference/VolumePreference.java | 36 | ||||
-rw-r--r-- | core/res/res/layout/preference_widget_seekbar.xml | 87 |
6 files changed, 404 insertions, 46 deletions
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index b6d1594..74a376d 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -29,6 +29,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.AbsSavedState; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -956,6 +957,17 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis context.startActivity(mIntent); } } + + /** + * Allows a Preference to intercept key events without having focus. + * For example, SeekBarPreference uses this to intercept +/- to adjust + * the progress. + * @return True if the Preference handled the key. Returns false by default. + * @hide + */ + public boolean onKey(View v, int keyCode, KeyEvent event) { + return false; + } /** * Returns the {@link android.content.Context} of this Preference. diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 488919c..f6ba7f7 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -20,13 +20,14 @@ import android.app.Activity; import android.app.Fragment; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.View.OnKeyListener; import android.widget.ListView; /** @@ -350,6 +351,22 @@ public abstract class PreferenceFragment extends Fragment implements "Your content must have a ListView whose id attribute is " + "'android.R.id.list'"); } + mList.setOnKeyListener(mListOnKeyListener); mHandler.post(mRequestFocus); } + + private OnKeyListener mListOnKeyListener = new OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + Object selectedItem = mList.getSelectedItem(); + if (selectedItem instanceof Preference) { + View selectedView = mList.getSelectedView(); + return ((Preference)selectedItem).onKey( + selectedView, keyCode, event); + } + return false; + } + + }; } diff --git a/core/java/android/preference/SeekBarDialogPreference.java b/core/java/android/preference/SeekBarDialogPreference.java new file mode 100644 index 0000000..0e89b16 --- /dev/null +++ b/core/java/android/preference/SeekBarDialogPreference.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 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. + */ + +package android.preference; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.SeekBar; + +/** + * @hide + */ +public class SeekBarDialogPreference extends DialogPreference { + private static final String TAG = "SeekBarDialogPreference"; + + private Drawable mMyIcon; + + public SeekBarDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog); + createActionButtons(); + + // Steal the XML dialogIcon attribute's value + mMyIcon = getDialogIcon(); + setDialogIcon(null); + } + + // Allow subclasses to override the action buttons + public void createActionButtons() { + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); + if (mMyIcon != null) { + iconView.setImageDrawable(mMyIcon); + } else { + iconView.setVisibility(View.GONE); + } + } + + protected static SeekBar getSeekBar(View dialogView) { + return (SeekBar) dialogView.findViewById(com.android.internal.R.id.seekbar); + } +} diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java index 037fb41..b8919c2 100644 --- a/core/java/android/preference/SeekBarPreference.java +++ b/core/java/android/preference/SeekBarPreference.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -17,51 +17,226 @@ package android.preference; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.preference.DialogPreference; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.View; -import android.widget.ImageView; import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; /** * @hide */ -public class SeekBarPreference extends DialogPreference { - private static final String TAG = "SeekBarPreference"; +public class SeekBarPreference extends Preference + implements OnSeekBarChangeListener { - private Drawable mMyIcon; + private int mProgress; + private int mMax; + private boolean mTrackingTouch; + + public SeekBarPreference( + Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ProgressBar, defStyle, 0); + setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax)); + a.recycle(); + setLayoutResource(com.android.internal.R.layout.preference_widget_seekbar); + } public SeekBarPreference(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); + } + + public SeekBarPreference(Context context) { + this(context, null); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + SeekBar seekBar = (SeekBar) view.findViewById( + com.android.internal.R.id.seekbar); + seekBar.setOnSeekBarChangeListener(this); + seekBar.setMax(mMax); + seekBar.setProgress(mProgress); + seekBar.setEnabled(isEnabled()); + } + + @Override + public CharSequence getSummary() { + return null; + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setProgress(restoreValue ? getPersistedInt(mProgress) + : (Integer) defaultValue); + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_UP) { + if (keyCode == KeyEvent.KEYCODE_PLUS + || keyCode == KeyEvent.KEYCODE_EQUALS) { + setProgress(getProgress() + 1); + return true; + } + if (keyCode == KeyEvent.KEYCODE_MINUS) { + setProgress(getProgress() - 1); + return true; + } + } + return false; + } + + public void setMax(int max) { + if (max != mMax) { + mMax = max; + notifyChanged(); + } + } + + public void setProgress(int progress) { + setProgress(progress, true); + } + + private void setProgress(int progress, boolean notifyChanged) { + if (progress > mMax) { + progress = mMax; + } + if (progress < 0) { + progress = 0; + } + if (progress != mProgress) { + mProgress = progress; + persistInt(progress); + if (notifyChanged) { + notifyChanged(); + } + } + } - setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog); - createActionButtons(); + public int getProgress() { + return mProgress; + } + + /** + * Persist the seekBar's progress value if callChangeListener + * returns true, otherwise set the seekBar's progress to the stored value + */ + void syncProgress(SeekBar seekBar) { + int progress = seekBar.getProgress(); + if (progress != mProgress) { + if (callChangeListener(progress)) { + setProgress(progress, false); + } else { + seekBar.setProgress(mProgress); + } + } + } + + @Override + public void onProgressChanged( + SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && !mTrackingTouch) { + syncProgress(seekBar); + } + } - // Steal the XML dialogIcon attribute's value - mMyIcon = getDialogIcon(); - setDialogIcon(null); + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mTrackingTouch = true; } - // Allow subclasses to override the action buttons - public void createActionButtons() { - setPositiveButtonText(android.R.string.ok); - setNegativeButtonText(android.R.string.cancel); + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mTrackingTouch = false; + if (seekBar.getProgress() != mProgress) { + syncProgress(seekBar); + } + } + + @Override + protected Parcelable onSaveInstanceState() { + /* + * Suppose a client uses this preference type without persisting. We + * must save the instance state so it is able to, for example, survive + * orientation changes. + */ + + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + // Save the instance state + final SavedState myState = new SavedState(superState); + myState.progress = mProgress; + myState.max = mMax; + return myState; } @Override - protected void onBindDialogView(View view) { - super.onBindDialogView(view); - - final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); - if (mMyIcon != null) { - iconView.setImageDrawable(mMyIcon); - } else { - iconView.setVisibility(View.GONE); + protected void onRestoreInstanceState(Parcelable state) { + if (!state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; } + + // Restore the instance state + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + mProgress = myState.progress; + mMax = myState.max; + notifyChanged(); } - protected static SeekBar getSeekBar(View dialogView) { - return (SeekBar) dialogView.findViewById(com.android.internal.R.id.seekbar); + /** + * SavedState, a subclass of {@link BaseSavedState}, will store the state + * of MyPreference, a subclass of Preference. + * <p> + * It is important to always call through to super methods. + */ + private static class SavedState extends BaseSavedState { + int progress; + int max; + + public SavedState(Parcel source) { + super(source); + + // Restore the click counter + progress = source.readInt(); + max = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + + // Save the click counter + dest.writeInt(progress); + dest.writeInt(max); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; } } diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index 3b12780..b48e8ce 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -38,19 +38,19 @@ import android.widget.SeekBar.OnSeekBarChangeListener; /** * @hide */ -public class VolumePreference extends SeekBarPreference implements +public class VolumePreference extends SeekBarDialogPreference implements PreferenceManager.OnActivityStopListener, View.OnKeyListener { private static final String TAG = "VolumePreference"; - + private int mStreamType; /** May be null if the dialog isn't visible. */ private SeekBarVolumizer mSeekBarVolumizer; - + public VolumePreference(Context context, AttributeSet attrs) { super(context, attrs); - + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.VolumePreference, 0, 0); mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0); @@ -64,7 +64,7 @@ public class VolumePreference extends SeekBarPreference implements @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); - + final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType); @@ -105,7 +105,7 @@ public class VolumePreference extends SeekBarPreference implements @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); - + if (!positiveResult && mSeekBarVolumizer != null) { mSeekBarVolumizer.revertVolume(); } @@ -222,16 +222,16 @@ public class VolumePreference extends SeekBarPreference implements private Context mContext; private Handler mHandler = new Handler(); - + private AudioManager mAudioManager; private int mStreamType; - private int mOriginalStreamVolume; + private int mOriginalStreamVolume; private Ringtone mRingtone; - + private int mLastProgress = -1; private SeekBar mSeekBar; private int mVolumeBeforeMute = -1; - + private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -263,7 +263,7 @@ public class VolumePreference extends SeekBarPreference implements mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); seekBar.setProgress(mOriginalStreamVolume); seekBar.setOnSeekBarChangeListener(this); - + mContext.getContentResolver().registerContentObserver( System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver); @@ -290,17 +290,17 @@ public class VolumePreference extends SeekBarPreference implements mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mSeekBar.setOnSeekBarChangeListener(null); } - + public void revertVolume() { mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0); } - + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { if (!fromTouch) { return; } - + postSetVolume(progress); } @@ -310,7 +310,7 @@ public class VolumePreference extends SeekBarPreference implements mHandler.removeCallbacks(this); mHandler.post(this); } - + public void onStartTrackingTouch(SeekBar seekBar) { } @@ -319,7 +319,7 @@ public class VolumePreference extends SeekBarPreference implements startSample(); } } - + public void run() { mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0); } @@ -334,7 +334,7 @@ public class VolumePreference extends SeekBarPreference implements mRingtone.play(); } } - + public void stopSample() { if (mRingtone != null) { mRingtone.stop(); @@ -344,7 +344,7 @@ public class VolumePreference extends SeekBarPreference implements public SeekBar getSeekBar() { return mSeekBar; } - + public void changeVolumeBy(int amount) { mSeekBar.incrementProgressBy(amount); if (!isSamplePlaying()) { diff --git a/core/res/res/layout/preference_widget_seekbar.xml b/core/res/res/layout/preference_widget_seekbar.xml new file mode 100644 index 0000000..e4cf86d --- /dev/null +++ b/core/res/res/layout/preference_widget_seekbar.xml @@ -0,0 +1,87 @@ +<?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. +--> + +<!-- 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:paddingRight="?android:attr/scrollbarSize"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center" + android:minWidth="@dimen/preference_icon_minWidth" + android:orientation="horizontal"> + <ImageView + android:id="@+android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:minWidth="48dp" + /> + </LinearLayout> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="16dip" + android:layout_marginRight="8dip" + 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/textAppearanceMedium" + 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_alignLeft="@android:id/title" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="4" /> + + <!-- 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:layout_below="@android:id/summary" + android:layout_alignLeft="@android:id/title" + android:minWidth="@dimen/preference_widget_width" + android:gravity="center" + android:orientation="vertical" /> + + <SeekBar android:id="@+android:id/seekbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/summary" + android:layout_toRightOf="@android:id/widget_frame" + android:layout_alignParentRight="true" /> + + </RelativeLayout> + +</LinearLayout> |