Home | History | Annotate | Download | only in widget
      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