1 /* 2 * Copyright (C) 2017 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 androidx.leanback.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.graphics.Rect; 26 import android.graphics.RectF; 27 import android.os.Bundle; 28 import android.util.AttributeSet; 29 import android.view.View; 30 31 import androidx.annotation.RestrictTo; 32 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 33 import androidx.leanback.R; 34 35 import org.jspecify.annotations.NonNull; 36 37 /** 38 * Replacement of SeekBar, has two bar heights and two thumb size when focused/not_focused. 39 * The widget does not deal with KeyEvent, it's client's responsibility to set a key listener. 40 */ 41 @RestrictTo(LIBRARY_GROUP_PREFIX) 42 public final class SeekBar extends View { 43 44 /** 45 */ 46 @RestrictTo(LIBRARY_GROUP_PREFIX) 47 public abstract static class AccessibilitySeekListener { 48 /** 49 * Called to perform AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD 50 */ onAccessibilitySeekForward()51 public abstract boolean onAccessibilitySeekForward(); 52 /** 53 * Called to perform AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD 54 */ onAccessibilitySeekBackward()55 public abstract boolean onAccessibilitySeekBackward(); 56 } 57 58 private final RectF mProgressRect = new RectF(); 59 private final RectF mSecondProgressRect = new RectF(); 60 private final RectF mBackgroundRect = new RectF(); 61 private final Paint mSecondProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 62 private final Paint mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 63 private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 64 private final Paint mKnobPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 65 66 private int mProgress; 67 private int mSecondProgress; 68 private int mMax; 69 private int mKnobx; 70 71 private int mActiveRadius; 72 private int mBarHeight; 73 private int mActiveBarHeight; 74 75 private AccessibilitySeekListener mAccessibilitySeekListener; 76 SeekBar(Context context, AttributeSet attrs)77 public SeekBar(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 setWillNotDraw(false); 80 mBackgroundPaint.setColor(Color.GRAY); 81 mSecondProgressPaint.setColor(Color.LTGRAY); 82 mProgressPaint.setColor(Color.RED); 83 mKnobPaint.setColor(Color.WHITE); 84 mBarHeight = context.getResources().getDimensionPixelSize( 85 R.dimen.lb_playback_transport_progressbar_bar_height); 86 mActiveBarHeight = context.getResources().getDimensionPixelSize( 87 R.dimen.lb_playback_transport_progressbar_active_bar_height); 88 mActiveRadius = context.getResources().getDimensionPixelSize( 89 R.dimen.lb_playback_transport_progressbar_active_radius); 90 } 91 92 /** 93 * Set radius in pixels for thumb when SeekBar is focused. 94 */ setActiveRadius(int radius)95 public void setActiveRadius(int radius) { 96 mActiveRadius = radius; 97 calculate(); 98 } 99 100 /** 101 * Set horizontal bar height in pixels when SeekBar is not focused. 102 */ setBarHeight(int barHeight)103 public void setBarHeight(int barHeight) { 104 mBarHeight = barHeight; 105 calculate(); 106 } 107 108 /** 109 * Set horizontal bar height in pixels when SeekBar is focused. 110 */ setActiveBarHeight(int activeBarHeight)111 public void setActiveBarHeight(int activeBarHeight) { 112 mActiveBarHeight = activeBarHeight; 113 calculate(); 114 } 115 116 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)117 protected void onFocusChanged(boolean gainFocus, 118 int direction, Rect previouslyFocusedRect) { 119 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 120 calculate(); 121 } 122 123 @Override onSizeChanged(final int w, final int h, final int oldw, final int oldh)124 protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { 125 super.onSizeChanged(w, h, oldw, oldh); 126 calculate(); 127 } 128 129 @Override onDraw(@onNull Canvas canvas)130 protected void onDraw(@NonNull Canvas canvas) { 131 super.onDraw(canvas); 132 final int radius = isFocused() ? mActiveRadius : mBarHeight / 2; 133 canvas.drawRoundRect(mBackgroundRect, radius, radius, mBackgroundPaint); 134 if (mSecondProgressRect.right > mSecondProgressRect.left) { 135 canvas.drawRoundRect(mSecondProgressRect, radius, radius, mSecondProgressPaint); 136 } 137 canvas.drawRoundRect(mProgressRect, radius, radius, mProgressPaint); 138 canvas.drawCircle(mKnobx, getHeight() / 2, radius, mKnobPaint); 139 } 140 141 /** 142 * Set progress within 0 and {@link #getMax()} 143 */ setProgress(int progress)144 public void setProgress(int progress) { 145 if (progress > mMax) { 146 progress = mMax; 147 } else if (progress < 0) { 148 progress = 0; 149 } 150 mProgress = progress; 151 calculate(); 152 } 153 154 /** 155 * Set secondary progress within 0 and {@link #getMax()} 156 */ setSecondaryProgress(int progress)157 public void setSecondaryProgress(int progress) { 158 if (progress > mMax) { 159 progress = mMax; 160 } else if (progress < 0) { 161 progress = 0; 162 } 163 mSecondProgress = progress; 164 calculate(); 165 } 166 167 /** 168 * Get progress within 0 and {@link #getMax()} 169 */ getProgress()170 public int getProgress() { 171 return mProgress; 172 } 173 174 /** 175 * Get secondary progress within 0 and {@link #getMax()} 176 */ getSecondProgress()177 public int getSecondProgress() { 178 return mSecondProgress; 179 } 180 181 /** 182 * Get max value. 183 */ getMax()184 public int getMax() { 185 return mMax; 186 } 187 188 /** 189 * Set max value. 190 */ setMax(int max)191 public void setMax(int max) { 192 this.mMax = max; 193 calculate(); 194 } 195 196 /** 197 * Set color for progress. 198 */ setProgressColor(int color)199 public void setProgressColor(int color) { 200 mProgressPaint.setColor(color); 201 } 202 203 /** 204 * Set color for second progress which is usually for buffering indication. 205 */ setSecondaryProgressColor(int color)206 public void setSecondaryProgressColor(int color) { 207 mSecondProgressPaint.setColor(color); 208 } 209 210 /** 211 * Set color for second progress which is usually for buffering indication. 212 */ getSecondaryProgressColor()213 public int getSecondaryProgressColor() { 214 return mSecondProgressPaint.getColor(); 215 } 216 calculate()217 private void calculate() { 218 final int barHeight = isFocused() ? mActiveBarHeight : mBarHeight; 219 220 final int width = getWidth(); 221 final int height = getHeight(); 222 final int verticalPadding = (height - barHeight) / 2; 223 224 mBackgroundRect.set(mBarHeight / 2, verticalPadding, 225 width - mBarHeight / 2, height - verticalPadding); 226 227 final int radius = isFocused() ? mActiveRadius : mBarHeight / 2; 228 final int progressWidth = width - radius * 2; 229 final float progressPixels = mProgress / (float) mMax * progressWidth; 230 mProgressRect.set(mBarHeight / 2, verticalPadding, mBarHeight / 2 + progressPixels, 231 height - verticalPadding); 232 233 final float secondProgressPixels = mSecondProgress / (float) mMax * progressWidth; 234 mSecondProgressRect.set(mProgressRect.right, verticalPadding, 235 mBarHeight / 2 + secondProgressPixels, height - verticalPadding); 236 237 mKnobx = radius + (int) progressPixels; 238 invalidate(); 239 } 240 241 @Override getAccessibilityClassName()242 public CharSequence getAccessibilityClassName() { 243 return android.widget.SeekBar.class.getName(); 244 } 245 setAccessibilitySeekListener(AccessibilitySeekListener listener)246 public void setAccessibilitySeekListener(AccessibilitySeekListener listener) { 247 mAccessibilitySeekListener = listener; 248 } 249 250 @Override performAccessibilityAction(int action, Bundle arguments)251 public boolean performAccessibilityAction(int action, Bundle arguments) { 252 if (mAccessibilitySeekListener != null) { 253 switch (action) { 254 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 255 return mAccessibilitySeekListener.onAccessibilitySeekForward(); 256 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 257 return mAccessibilitySeekListener.onAccessibilitySeekBackward(); 258 } 259 } 260 return super.performAccessibilityAction(action, arguments); 261 } 262 263 } 264