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; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.ImageView; 26 import android.widget.SeekBar; 27 import android.widget.TextView; 28 29 import androidx.core.content.res.TypedArrayUtils; 30 import androidx.preference.PreferenceViewHolder; 31 32 import com.android.internal.util.Preconditions; 33 import com.android.settings.R; 34 import com.android.settings.Utils; 35 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 */ 54 public class LabeledSeekBarPreference extends SeekBarPreference { 55 56 private final int mTextStartId; 57 private final int mTextEndId; 58 private final int mTickMarkId; 59 private int mIconStartId; 60 private int mIconEndId; 61 private int mIconStartContentDescriptionId; 62 private int mIconEndContentDescriptionId; 63 private OnPreferenceChangeListener mStopListener; 64 private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener; 65 66 private SeekBar mSeekBar; 67 LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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( 77 R.styleable.LabeledSeekBarPreference_textStart, /* defValue= */ 0); 78 mTextEndId = styledAttrs.getResourceId( 79 R.styleable.LabeledSeekBarPreference_textEnd, /* defValue= */ 0); 80 mTickMarkId = styledAttrs.getResourceId( 81 R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0); 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."); 100 styledAttrs.recycle(); 101 } 102 LabeledSeekBarPreference(Context context, AttributeSet attrs)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 getSeekbar()109 public SeekBar getSeekbar() { 110 return mSeekBar; 111 } 112 113 /** Set the start icon of the Seekbar. */ setIconStart(int iconStartId)114 public void setIconStart(int iconStartId) { 115 if (mIconStartId != iconStartId) { 116 mIconStartId = iconStartId; 117 notifyChanged(); 118 } 119 } 120 121 /** Set the description resource id of the start icon. */ setIconStartContentDescription(int iconStartContentDescriptionId)122 public void setIconStartContentDescription(int iconStartContentDescriptionId) { 123 if (mIconStartContentDescriptionId != iconStartContentDescriptionId) { 124 mIconStartContentDescriptionId = iconStartContentDescriptionId; 125 notifyChanged(); 126 } 127 } 128 129 /** Set the end icon of the Seekbar. */ setIconEnd(int iconEndId)130 public void setIconEnd(int iconEndId) { 131 if (mIconEndId != iconEndId) { 132 mIconEndId = iconEndId; 133 notifyChanged(); 134 } 135 } 136 137 /** Set the description resource id of the end icon. */ setIconEndContentDescription(int iconEndContentDescriptionId)138 public void setIconEndContentDescription(int iconEndContentDescriptionId) { 139 if (mIconEndContentDescriptionId != iconEndContentDescriptionId) { 140 mIconEndContentDescriptionId = iconEndContentDescriptionId; 141 notifyChanged(); 142 } 143 } 144 145 @Override onBindViewHolder(PreferenceViewHolder holder)146 public void onBindViewHolder(PreferenceViewHolder holder) { 147 super.onBindViewHolder(holder); 148 149 final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); 150 boolean isSummaryVisible = false; 151 if (summaryView != null) { 152 isSummaryVisible = (summaryView.getVisibility() == View.VISIBLE); 153 } 154 final TextView titleView = (TextView) holder.findViewById(android.R.id.title); 155 if (titleView != null && !isSelectable() && isEnabled() && isSummaryVisible) { 156 titleView.setTextColor( 157 Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)); 158 } 159 160 final TextView startText = (TextView) holder.findViewById(android.R.id.text1); 161 if (mTextStartId > 0) { 162 startText.setText(mTextStartId); 163 } 164 165 final TextView endText = (TextView) holder.findViewById(android.R.id.text2); 166 if (mTextEndId > 0) { 167 endText.setText(mTextEndId); 168 } 169 170 final View labelFrame = holder.findViewById(R.id.label_frame); 171 final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0; 172 labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE); 173 174 mSeekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar); 175 if (mTickMarkId != 0) { 176 final Drawable tickMark = getContext().getDrawable(mTickMarkId); 177 mSeekBar.setTickMark(tickMark); 178 } 179 180 final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame); 181 final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start); 182 updateIconStartIfNeeded(iconStartFrame, iconStartView, mSeekBar); 183 184 final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame); 185 final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end); 186 updateIconEndIfNeeded(iconEndFrame, iconEndView, mSeekBar); 187 } 188 setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener)189 public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) { 190 mStopListener = listener; 191 } 192 193 @Override onStartTrackingTouch(SeekBar seekBar)194 public void onStartTrackingTouch(SeekBar seekBar) { 195 super.onStartTrackingTouch(seekBar); 196 197 if (mSeekBarChangeListener != null) { 198 mSeekBarChangeListener.onStartTrackingTouch(seekBar); 199 } 200 } 201 202 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)203 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 204 super.onProgressChanged(seekBar, progress, fromUser); 205 206 if (mSeekBarChangeListener != null) { 207 mSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); 208 } 209 } 210 211 @Override onStopTrackingTouch(SeekBar seekBar)212 public void onStopTrackingTouch(SeekBar seekBar) { 213 super.onStopTrackingTouch(seekBar); 214 215 if (mSeekBarChangeListener != null) { 216 mSeekBarChangeListener.onStopTrackingTouch(seekBar); 217 } 218 219 if (mStopListener != null) { 220 mStopListener.onPreferenceChange(this, seekBar.getProgress()); 221 } 222 223 // Need to update the icon enabled status 224 notifyChanged(); 225 } 226 setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener)227 public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener) { 228 mSeekBarChangeListener = seekBarChangeListener; 229 } 230 updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart, SeekBar seekBar)231 private void updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart, 232 SeekBar seekBar) { 233 if (mIconStartId == 0) { 234 return; 235 } 236 237 if (iconStart.getDrawable() == null) { 238 iconStart.setImageResource(mIconStartId); 239 } 240 241 if (mIconStartContentDescriptionId != 0) { 242 final String contentDescription = 243 iconFrame.getContext().getString(mIconStartContentDescriptionId); 244 iconFrame.setContentDescription(contentDescription); 245 } 246 247 iconFrame.setOnClickListener((view) -> { 248 final int progress = getProgress(); 249 if (progress > 0) { 250 setProgress(progress - 1); 251 } 252 }); 253 254 iconFrame.setVisibility(View.VISIBLE); 255 setIconViewAndFrameEnabled(iconStart, seekBar.getProgress() > 0); 256 } 257 updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar)258 private void updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar) { 259 if (mIconEndId == 0) { 260 return; 261 } 262 263 if (iconEnd.getDrawable() == null) { 264 iconEnd.setImageResource(mIconEndId); 265 } 266 267 if (mIconEndContentDescriptionId != 0) { 268 final String contentDescription = 269 iconFrame.getContext().getString(mIconEndContentDescriptionId); 270 iconFrame.setContentDescription(contentDescription); 271 } 272 273 iconFrame.setOnClickListener((view) -> { 274 final int progress = getProgress(); 275 if (progress < getMax()) { 276 setProgress(progress + 1); 277 } 278 }); 279 280 iconFrame.setVisibility(View.VISIBLE); 281 setIconViewAndFrameEnabled(iconEnd, seekBar.getProgress() < seekBar.getMax()); 282 } 283 setIconViewAndFrameEnabled(View iconView, boolean enabled)284 private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) { 285 iconView.setEnabled(enabled); 286 final ViewGroup iconFrame = (ViewGroup) iconView.getParent(); 287 iconFrame.setEnabled(enabled); 288 } 289 } 290 291