Files
aps3e/app/src/main/java/aenu/preference/SeekBarPreference.java
T
2025-08-18 23:04:01 +08:00

480 lines
17 KiB
Java

// SPDX-License-Identifier: WTFPL
package aenu.preference;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.R;
//modify from https://github.com/androidx/androidx/blob/androidx-main/preference/preference/src/main/java/androidx/preference/SeekBarPreference.java
public class SeekBarPreference extends DialogPreference {
public static class SeekBarPreferenceFragmentCompat extends PreferenceDialogFragmentCompat {
private static final String SAVE_STATE_VALUE = "SeekBarPreferenceFragmentCompat.value";
private static final String SAVE_STATE_MIN = "SeekBarPreferenceFragmentCompat.min";
private static final String SAVE_STATE_MAX =
"SeekBarPreferenceFragmentCompat.max";
int m_value;
int m_min;
int m_max;
EditText mEditText;
@NonNull
public static SeekBarPreference.SeekBarPreferenceFragmentCompat newInstance(String key) {
final SeekBarPreference.SeekBarPreferenceFragmentCompat fragment =
new SeekBarPreference.SeekBarPreferenceFragmentCompat();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
final SeekBarPreference preference = getSeekBarPreference();
m_value = preference.getValue();
m_min = preference.getMin();
m_max = preference.getMax();
} else {
m_value = savedInstanceState.getInt(SAVE_STATE_VALUE);
m_min = savedInstanceState.getInt(SAVE_STATE_MIN);
m_max = savedInstanceState.getInt(SAVE_STATE_MAX);
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVE_STATE_VALUE, m_value);
outState.putInt(SAVE_STATE_MIN, m_min);
outState.putInt(SAVE_STATE_MAX, m_max);
}
private SeekBarPreference getSeekBarPreference() {
return (SeekBarPreference) getPreference();
}
@Override
protected void onBindDialogView(@NonNull View view) {
super.onBindDialogView(view);
mEditText = (EditText) view.findViewById(android.R.id.edit);
if(mEditText!=null){
mEditText.setHint(Integer.toString(m_min)+" -> "+Integer.toString(m_max));
}
}
@Override
protected void onPrepareDialogBuilder(@NonNull AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
// The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
// dialog instead of the user having to press 'Ok'.
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(mEditText!=null){
try{
int value = Integer.parseInt(mEditText.getText().toString());
if (value >= m_min && value <= m_max){
final SeekBarPreference preference = getSeekBarPreference();
if(preference.callChangeListener(value))
preference.setValue(value);
}
}catch(Exception e){
Log.e("SeekbarPreference",e.toString());
}
}
dialog.dismiss();
}
});
}
@Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
try{
int value = Integer.parseInt(mEditText.getText().toString());
if (value >= m_min && value <= m_max){
final SeekBarPreference preference = getSeekBarPreference();
if(preference.callChangeListener(value))
preference.setValue(value);
}
}catch(Exception e){
Log.e("SeekbarPreference",e.toString());
}
}
}
}
int mSeekBarValue;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mMin;
private int mMax;
private int mSeekBarIncrement;
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mTrackingTouch;
@SuppressWarnings("WeakerAccess") /* synthetic access */
SeekBar mSeekBar;
private TextView mSeekBarValueTextView;
// Whether the SeekBar should respond to the left/right keys
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mAdjustable;
// Whether to show the SeekBar value TextView next to the bar
private boolean mShowSeekBarValue;
// Whether the SeekBarPreference should continuously save the Seekbar value while it is being
// dragged.
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mUpdatesContinuously;
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser && (mUpdatesContinuously || !mTrackingTouch)) {
syncValueInternal(seekBar);
} else {
// We always want to update the text while the seekbar is being dragged
updateLabelValue(progress + mMin);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mTrackingTouch = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mTrackingTouch = false;
if (seekBar.getProgress() + mMin != mSeekBarValue) {
syncValueInternal(seekBar);
}
}
};
/**
* Listener reacting to the user pressing DPAD left/right keys if {@code
* adjustable} attribute is set to true; it transfers the key presses to the {@link SeekBar}
* to be handled accordingly.
*/
private final View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
if (!mAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
// Right or left keys are pressed when in non-adjustable mode; Skip the keys.
return false;
}
// We don't want to propagate the click keys down to the SeekBar view since it will
// create the ripple effect for the thumb.
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
return false;
}
if (mSeekBar == null) {
return false;
}
return mSeekBar.onKeyDown(keyCode, event);
}
};
public SeekBarPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setDialogLayoutResource(aenu.aps3e.R.layout.edit_seek_bar);
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
// The ordering of these two statements are important. If we want to set max first, we need
// to perform the same steps by changing min/max to max/min as following:
// mMax = a.getInt(...) and setMin(...).
mMin = a.getInt(R.styleable.SeekBarPreference_min, 0);
setMax(a.getInt(R.styleable.SeekBarPreference_android_max, 100));
setSeekBarIncrement(a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0));
mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, false);
mUpdatesContinuously = a.getBoolean(R.styleable.SeekBarPreference_updatesContinuously,
false);
a.recycle();
a = context.obtainStyledAttributes(attrs,
new int[]{android.R.attr.textColorPrimary}, defStyleAttr, defStyleRes);
title_color=a.getColor(0, Color.RED);
a.recycle();
}
public SeekBarPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SeekBarPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.seekBarPreferenceStyle);
}
public SeekBarPreference(@NonNull Context context) {
this(context, null);
}
TextView title_view;
int title_color;
boolean is_modify_color=false;
int modify_color= Color.MAGENTA;
public int get_modify_color(){
return modify_color;
}
public void set_modify_color(int color){
modify_color=color;
}
public void set_is_modify_color(boolean is_modify_color){
this.is_modify_color=is_modify_color;
}
public boolean get_is_modify_color(){
return is_modify_color;
}
@Override
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TextView title_v=(TextView) holder.itemView.findViewById(android.R.id.title);
title_view=title_v;
if(is_modify_color){
if(title_v!=null)
title_v.setTextColor(modify_color);
}
else{
if(title_v!=null)
title_v.setTextColor(title_v.isEnabled()?title_color:Color.GRAY);
}
holder.itemView.setOnKeyListener(mSeekBarKeyListener);
mSeekBar = (SeekBar) holder.findViewById(R.id.seekbar);
mSeekBarValueTextView = (TextView) holder.findViewById(R.id.seekbar_value);
if (mShowSeekBarValue) {
mSeekBarValueTextView.setVisibility(View.VISIBLE);
mSeekBarValueTextView.setTextColor(is_modify_color?modify_color:title_color);
} else {
mSeekBarValueTextView.setVisibility(View.GONE);
mSeekBarValueTextView = null;
}
if (mSeekBar == null) {
//Log.e(TAG, "SeekBar view is null in onBindViewHolder.");
return;
}
mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
mSeekBar.setMax(mMax - mMin);
// If the increment is not zero, use that. Otherwise, use the default mKeyProgressIncrement
// in AbsSeekBar when it's zero. This default increment value is set by AbsSeekBar
// after calling setMax. That's why it's important to call setKeyProgressIncrement after
// calling setMax() since setMax() can change the increment value.
if (mSeekBarIncrement != 0) {
mSeekBar.setKeyProgressIncrement(mSeekBarIncrement);
} else {
mSeekBarIncrement = mSeekBar.getKeyProgressIncrement();
}
mSeekBar.setProgress(mSeekBarValue - mMin);
updateLabelValue(mSeekBarValue);
mSeekBar.setEnabled(isEnabled());
}
@Override
protected void onSetInitialValue(Object defaultValue) {
if (defaultValue == null) {
defaultValue = 0;
}
setValue(getPersistedInt((Integer) defaultValue));
}
@Override
protected @Nullable Object onGetDefaultValue(@NonNull TypedArray a, int index) {
return a.getInt(index, 0);
}
public int getMin() {
return mMin;
}
/**
* Sets the lower bound on the {@link SeekBar}.
*
* @param min The lower bound to set
*/
public void setMin(int min) {
if (min > mMax) {
min = mMax;
}
if (min != mMin) {
mMin = min;
notifyChanged();
}
}
public int getValue() {
return mSeekBarValue;
}
/**
* Sets the current progress of the {@link SeekBar}.
*
* @param seekBarValue The current progress of the {@link SeekBar}
*/
public void setValue(int seekBarValue) {
setValueInternal(seekBarValue, true);
}
public final void setSeekBarIncrement(int seekBarIncrement) {
if (seekBarIncrement != mSeekBarIncrement) {
mSeekBarIncrement = Math.min(mMax - mMin, Math.abs(seekBarIncrement));
notifyChanged();
}
}
private void setValueInternal(int seekBarValue, boolean notifyChanged) {
if (seekBarValue < mMin) {
seekBarValue = mMin;
}
if (seekBarValue > mMax) {
seekBarValue = mMax;
}
if (seekBarValue != mSeekBarValue) {
mSeekBarValue = seekBarValue;
updateLabelValue(mSeekBarValue);
persistInt(seekBarValue);
if (notifyChanged) {
notifyChanged();
}
}
}
public int getMax() {
return mMax;
}
/**
* Sets the upper bound on the {@link SeekBar}.
*
* @param max The upper bound to set
*/
public final void setMax(int max) {
if (max < mMin) {
max = mMin;
}
if (max != mMax) {
mMax = max;
notifyChanged();
}
}
void syncValueInternal(@NonNull SeekBar seekBar) {
int seekBarValue = mMin + seekBar.getProgress();
if (seekBarValue != mSeekBarValue) {
if (callChangeListener(seekBarValue)) {
setValueInternal(seekBarValue, false);
} else {
seekBar.setProgress(mSeekBarValue - mMin);
updateLabelValue(mSeekBarValue);
}
}
}
/**
* Attempts to update the TextView label that displays the current value.
*
* @param value the value to display next to the {@link SeekBar}
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void updateLabelValue(int value) {
if (mSeekBarValueTextView != null) {
mSeekBarValueTextView.setTextColor(is_modify_color?modify_color:title_color);
mSeekBarValueTextView.setText(String.valueOf(value));
}
if(title_view!=null){
if(title_view.isEnabled())
title_view.setTextColor(is_modify_color?modify_color:title_color);
else
title_view.setTextColor(Color.GRAY);
}
}
private static class SavedState extends BaseSavedState {
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
int mSeekBarValue;
int mMin;
int mMax;
SavedState(Parcel source) {
super(source);
// Restore the click counter
mSeekBarValue = source.readInt();
mMin = source.readInt();
mMax = source.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Save the click counter
dest.writeInt(mSeekBarValue);
dest.writeInt(mMin);
dest.writeInt(mMax);
}
}
}