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; 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 /** 36 * Replacement of SeekBar, has two bar heights and two thumb size when focused/not_focused. 37 * The widget does not deal with KeyEvent, it's client's responsibility to set a key listener. 38 * @hide 39 */ 40 @RestrictTo(LIBRARY_GROUP) 41 public final class SeekBar extends View { 42 43 /** 44 * @hide 45 */ 46 @RestrictTo(LIBRARY_GROUP) 47 public abstract static class AccessibilitySeekListener { 48 /** 49 * Called to perform AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD 50 */ 51 public abstract boolean onAccessibilitySeekForward(); 52 /** 53 * Called to perform AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD 54 */ 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 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 */ 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 */ 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 */ 111 public void setActiveBarHeight(int activeBarHeight) { 112 mActiveBarHeight = activeBarHeight; 113 calculate(); 114 } 115 116 @Override 117 protected void onFocusChanged(boolean gainFocus, 118 int direction, Rect previouslyFocusedRect) { 119 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 120 calculate(); 121 } 122 123 @Override 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 130 protected void onDraw(Canvas canvas) { 131 super.onDraw(canvas); 132 final int radius = isFocused() ? mActiveRadius : mBarHeight / 2; 133 canvas.drawRoundRect(mBackgroundRect, radius, radius, mBackgroundPaint); 134 canvas.drawRoundRect(mSecondProgressRect, radius, radius, mProgressPaint); 135 canvas.drawRoundRect(mProgressRect, radius, radius, mProgressPaint); 136 canvas.drawCircle(mKnobx, getHeight() / 2, radius, mKnobPaint); 137 } 138 139 /** 140 * Set progress within 0 and {@link #getMax()} 141 */ 142 public void setProgress(int progress) { 143 if (progress > mMax) { 144 progress = mMax; 145 } else if (progress < 0) { 146 progress = 0; 147 } 148 mProgress = progress; 149 calculate(); 150 } 151 152 /** 153 * Set secondary progress within 0 and {@link #getMax()} 154 */ 155 public void setSecondaryProgress(int progress) { 156 if (progress > mMax) { 157 progress = mMax; 158 } else if (progress < 0) { 159 progress = 0; 160 } 161 mSecondProgress = progress; 162 calculate(); 163 } 164 165 /** 166 * Get progress within 0 and {@link #getMax()} 167 */ 168 public int getProgress() { 169 return mProgress; 170 } 171 172 /** 173 * Get secondary progress within 0 and {@link #getMax()} 174 */ 175 public int getSecondProgress() { 176 return mSecondProgress; 177 } 178 179 /** 180 * Get max value. 181 */ 182 public int getMax() { 183 return mMax; 184 } 185 186 /** 187 * Set max value. 188 */ 189 public void setMax(int max) { 190 this.mMax = max; 191 calculate(); 192 } 193 194 /** 195 * Set color for progress. 196 */ 197 public void setProgressColor(int color) { 198 mProgressPaint.setColor(color); 199 } 200 201 private void calculate() { 202 final int barHeight = isFocused() ? mActiveBarHeight : mBarHeight; 203 204 final int width = getWidth(); 205 final int height = getHeight(); 206 final int verticalPadding = (height - barHeight) / 2; 207 208 mBackgroundRect.set(mBarHeight / 2, verticalPadding, 209 width - mBarHeight / 2, height - verticalPadding); 210 211 final int radius = isFocused() ? mActiveRadius : mBarHeight / 2; 212 final int progressWidth = width - radius * 2; 213 final float progressPixels = mProgress / (float) mMax * progressWidth; 214 mProgressRect.set(mBarHeight / 2, verticalPadding, mBarHeight / 2 + progressPixels, 215 height - verticalPadding); 216 217 final float secondProgressPixels = mSecondProgress / (float) mMax * progressWidth; 218 mSecondProgressRect.set(mBarHeight / 2, verticalPadding, 219 mBarHeight / 2 + secondProgressPixels, height - verticalPadding); 220 221 mKnobx = radius + (int) progressPixels; 222 invalidate(); 223 } 224 225 @Override 226 public CharSequence getAccessibilityClassName() { 227 return android.widget.SeekBar.class.getName(); 228 } 229 230 public void setAccessibilitySeekListener(AccessibilitySeekListener listener) { 231 mAccessibilitySeekListener = listener; 232 } 233 234 @Override 235 public boolean performAccessibilityAction(int action, Bundle arguments) { 236 if (mAccessibilitySeekListener != null) { 237 switch (action) { 238 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 239 return mAccessibilitySeekListener.onAccessibilitySeekForward(); 240 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 241 return mAccessibilitySeekListener.onAccessibilitySeekBackward(); 242 } 243 } 244 return super.performAccessibilityAction(action, arguments); 245 } 246 247 } 248