Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 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 | |
| 17 | package com.android.settings.widget; |
| 18 | |
| 19 | import android.content.Context; |
| 20 | import android.content.res.TypedArray; |
| 21 | import android.graphics.drawable.Drawable; |
| 22 | import android.util.AttributeSet; |
Govinda Wasserman | 3869aac | 2021-08-20 17:06:40 +0000 | [diff] [blame] | 23 | import android.view.View; |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 24 | import android.view.ViewGroup; |
| 25 | import android.widget.ImageView; |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 26 | import android.widget.SeekBar; |
| 27 | import android.widget.TextView; |
| 28 | |
| 29 | import androidx.core.content.res.TypedArrayUtils; |
| 30 | import androidx.preference.PreferenceViewHolder; |
| 31 | |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 32 | import com.android.internal.util.Preconditions; |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 33 | import com.android.settings.R; |
Edgar Wang | 2b0d9cf | 2022-05-19 09:20:30 +0800 | [diff] [blame] | 34 | import com.android.settings.Utils; |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 35 | |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 36 | /** |
| 37 | * A labeled {@link SeekBarPreference} with left and right text label, icon label, or both. |
| 38 | * |
| 39 | * <p> |
| 40 | * The component provides the attribute usage below. |
| 41 | * <attr name="textStart" format="reference" /> |
| 42 | * <attr name="textEnd" format="reference" /> |
| 43 | * <attr name="tickMark" format="reference" /> |
| 44 | * <attr name="iconStart" format="reference" /> |
| 45 | * <attr name="iconEnd" format="reference" /> |
| 46 | * <attr name="iconStartContentDescription" format="reference" /> |
| 47 | * <attr name="iconEndContentDescription" format="reference" /> |
| 48 | * </p> |
| 49 | * |
| 50 | * <p> If you set the attribute values {@code iconStartContentDescription} or {@code |
| 51 | * iconEndContentDescription} from XML, you must also set the corresponding attributes {@code |
| 52 | * iconStart} or {@code iconEnd}, otherwise throws an {@link IllegalArgumentException}.</p> |
| 53 | */ |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 54 | public class LabeledSeekBarPreference extends SeekBarPreference { |
| 55 | |
| 56 | private final int mTextStartId; |
| 57 | private final int mTextEndId; |
| 58 | private final int mTickMarkId; |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 59 | private final int mIconStartId; |
| 60 | private final int mIconEndId; |
| 61 | private final int mIconStartContentDescriptionId; |
| 62 | private final int mIconEndContentDescriptionId; |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 63 | private OnPreferenceChangeListener mStopListener; |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 64 | private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener; |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 65 | |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 66 | private SeekBar mSeekBar; |
| 67 | |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 68 | public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, |
| 69 | int defStyleRes) { |
| 70 | |
| 71 | super(context, attrs, defStyleAttr, defStyleRes); |
| 72 | setLayoutResource(R.layout.preference_labeled_slider); |
| 73 | |
| 74 | final TypedArray styledAttrs = context.obtainStyledAttributes(attrs, |
| 75 | R.styleable.LabeledSeekBarPreference); |
| 76 | mTextStartId = styledAttrs.getResourceId( |
Peter_Liang | 88d63ea | 2022-01-20 01:32:58 +0800 | [diff] [blame] | 77 | R.styleable.LabeledSeekBarPreference_textStart, /* defValue= */ 0); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 78 | mTextEndId = styledAttrs.getResourceId( |
Peter_Liang | 88d63ea | 2022-01-20 01:32:58 +0800 | [diff] [blame] | 79 | R.styleable.LabeledSeekBarPreference_textEnd, /* defValue= */ 0); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 80 | mTickMarkId = styledAttrs.getResourceId( |
| 81 | R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0); |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 82 | mIconStartId = styledAttrs.getResourceId( |
| 83 | R.styleable.LabeledSeekBarPreference_iconStart, /* defValue= */ 0); |
| 84 | mIconEndId = styledAttrs.getResourceId( |
| 85 | R.styleable.LabeledSeekBarPreference_iconEnd, /* defValue= */ 0); |
| 86 | |
| 87 | mIconStartContentDescriptionId = styledAttrs.getResourceId( |
| 88 | R.styleable.LabeledSeekBarPreference_iconStartContentDescription, |
| 89 | /* defValue= */ 0); |
| 90 | Preconditions.checkArgument(!(mIconStartContentDescriptionId != 0 && mIconStartId == 0), |
| 91 | "The resource of the iconStart attribute may be invalid or not set, " |
| 92 | + "you should set the iconStart attribute and have the valid resource."); |
| 93 | |
| 94 | mIconEndContentDescriptionId = styledAttrs.getResourceId( |
| 95 | R.styleable.LabeledSeekBarPreference_iconEndContentDescription, |
| 96 | /* defValue= */ 0); |
| 97 | Preconditions.checkArgument(!(mIconEndContentDescriptionId != 0 && mIconEndId == 0), |
| 98 | "The resource of the iconEnd attribute may be invalid or not set, " |
| 99 | + "you should set the iconEnd attribute and have the valid resource."); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 100 | styledAttrs.recycle(); |
| 101 | } |
| 102 | |
| 103 | public LabeledSeekBarPreference(Context context, AttributeSet attrs) { |
| 104 | this(context, attrs, TypedArrayUtils.getAttr(context, |
| 105 | androidx.preference.R.attr.seekBarPreferenceStyle, |
| 106 | com.android.internal.R.attr.seekBarPreferenceStyle), 0); |
| 107 | } |
| 108 | |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 109 | public SeekBar getSeekbar() { |
| 110 | return mSeekBar; |
| 111 | } |
| 112 | |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 113 | @Override |
| 114 | public void onBindViewHolder(PreferenceViewHolder holder) { |
| 115 | super.onBindViewHolder(holder); |
| 116 | |
Edgar Wang | 2b0d9cf | 2022-05-19 09:20:30 +0800 | [diff] [blame] | 117 | final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); |
| 118 | boolean isSummaryVisible = false; |
| 119 | if (summaryView != null) { |
| 120 | isSummaryVisible = (summaryView.getVisibility() == View.VISIBLE); |
| 121 | } |
| 122 | final TextView titleView = (TextView) holder.findViewById(android.R.id.title); |
| 123 | if (titleView != null && !isSelectable() && isEnabled() && isSummaryVisible) { |
| 124 | titleView.setTextColor( |
| 125 | Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)); |
| 126 | } |
| 127 | |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 128 | final TextView startText = (TextView) holder.findViewById(android.R.id.text1); |
Peter_Liang | 88d63ea | 2022-01-20 01:32:58 +0800 | [diff] [blame] | 129 | if (mTextStartId > 0) { |
| 130 | startText.setText(mTextStartId); |
| 131 | } |
| 132 | |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 133 | final TextView endText = (TextView) holder.findViewById(android.R.id.text2); |
Peter_Liang | 88d63ea | 2022-01-20 01:32:58 +0800 | [diff] [blame] | 134 | if (mTextEndId > 0) { |
| 135 | endText.setText(mTextEndId); |
| 136 | } |
| 137 | |
| 138 | final View labelFrame = holder.findViewById(R.id.label_frame); |
| 139 | final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0; |
| 140 | labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 141 | |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 142 | mSeekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 143 | if (mTickMarkId != 0) { |
| 144 | final Drawable tickMark = getContext().getDrawable(mTickMarkId); |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 145 | mSeekBar.setTickMark(tickMark); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 146 | } |
Govinda Wasserman | 3869aac | 2021-08-20 17:06:40 +0000 | [diff] [blame] | 147 | |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 148 | final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame); |
| 149 | final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start); |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 150 | updateIconStartIfNeeded(iconStartFrame, iconStartView, mSeekBar); |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 151 | |
| 152 | final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame); |
| 153 | final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end); |
Candice Lo | b132ada | 2023-03-20 08:17:26 +0000 | [diff] [blame] | 154 | updateIconEndIfNeeded(iconEndFrame, iconEndView, mSeekBar); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 155 | } |
| 156 | |
| 157 | public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) { |
| 158 | mStopListener = listener; |
| 159 | } |
| 160 | |
| 161 | @Override |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 162 | public void onStartTrackingTouch(SeekBar seekBar) { |
| 163 | super.onStartTrackingTouch(seekBar); |
| 164 | |
| 165 | if (mSeekBarChangeListener != null) { |
| 166 | mSeekBarChangeListener.onStartTrackingTouch(seekBar); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | @Override |
| 171 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { |
| 172 | super.onProgressChanged(seekBar, progress, fromUser); |
| 173 | |
| 174 | if (mSeekBarChangeListener != null) { |
| 175 | mSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | @Override |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 180 | public void onStopTrackingTouch(SeekBar seekBar) { |
| 181 | super.onStopTrackingTouch(seekBar); |
| 182 | |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 183 | if (mSeekBarChangeListener != null) { |
| 184 | mSeekBarChangeListener.onStopTrackingTouch(seekBar); |
| 185 | } |
| 186 | |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 187 | if (mStopListener != null) { |
| 188 | mStopListener.onPreferenceChange(this, seekBar.getProgress()); |
| 189 | } |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 190 | |
| 191 | // Need to update the icon enabled status |
| 192 | notifyChanged(); |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 193 | } |
Govinda Wasserman | 3869aac | 2021-08-20 17:06:40 +0000 | [diff] [blame] | 194 | |
Peter_Liang | cc5808c | 2022-01-19 21:41:17 +0800 | [diff] [blame] | 195 | public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener) { |
| 196 | mSeekBarChangeListener = seekBarChangeListener; |
| 197 | } |
| 198 | |
| 199 | private void updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart, |
| 200 | SeekBar seekBar) { |
| 201 | if (mIconStartId == 0) { |
| 202 | return; |
| 203 | } |
| 204 | |
| 205 | if (iconStart.getDrawable() == null) { |
| 206 | iconStart.setImageResource(mIconStartId); |
| 207 | } |
| 208 | |
| 209 | if (mIconStartContentDescriptionId != 0) { |
| 210 | final String contentDescription = |
| 211 | iconFrame.getContext().getString(mIconStartContentDescriptionId); |
| 212 | iconFrame.setContentDescription(contentDescription); |
| 213 | } |
| 214 | |
| 215 | iconFrame.setOnClickListener((view) -> { |
| 216 | final int progress = getProgress(); |
| 217 | if (progress > 0) { |
| 218 | setProgress(progress - 1); |
| 219 | } |
| 220 | }); |
| 221 | |
| 222 | iconFrame.setVisibility(View.VISIBLE); |
| 223 | setIconViewAndFrameEnabled(iconStart, seekBar.getProgress() > 0); |
| 224 | } |
| 225 | |
| 226 | private void updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar) { |
| 227 | if (mIconEndId == 0) { |
| 228 | return; |
| 229 | } |
| 230 | |
| 231 | if (iconEnd.getDrawable() == null) { |
| 232 | iconEnd.setImageResource(mIconEndId); |
| 233 | } |
| 234 | |
| 235 | if (mIconEndContentDescriptionId != 0) { |
| 236 | final String contentDescription = |
| 237 | iconFrame.getContext().getString(mIconEndContentDescriptionId); |
| 238 | iconFrame.setContentDescription(contentDescription); |
| 239 | } |
| 240 | |
| 241 | iconFrame.setOnClickListener((view) -> { |
| 242 | final int progress = getProgress(); |
| 243 | if (progress < getMax()) { |
| 244 | setProgress(progress + 1); |
| 245 | } |
| 246 | }); |
| 247 | |
| 248 | iconFrame.setVisibility(View.VISIBLE); |
| 249 | setIconViewAndFrameEnabled(iconEnd, seekBar.getProgress() < seekBar.getMax()); |
| 250 | } |
| 251 | |
| 252 | private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) { |
| 253 | iconView.setEnabled(enabled); |
| 254 | final ViewGroup iconFrame = (ViewGroup) iconView.getParent(); |
| 255 | iconFrame.setEnabled(enabled); |
| 256 | } |
Edgar Wang | cdff5c6 | 2021-06-08 09:10:41 +0000 | [diff] [blame] | 257 | } |
| 258 | |