Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 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 android.widget;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.UnsupportedAppUsage;
     22 import android.graphics.Canvas;
     23 import android.graphics.ColorFilter;
     24 import android.graphics.PixelFormat;
     25 import android.graphics.Rect;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Build;
     28 import android.view.View;
     29 
     30 import com.android.internal.widget.ScrollBarUtils;
     31 
     32 /**
     33  * This is only used by View for displaying its scroll bars. It should probably
     34  * be moved in to the view package since it is used in that lower-level layer.
     35  * For now, we'll hide it so it can be cleaned up later.
     36  *
     37  * {@hide}
     38  */
     39 public class ScrollBarDrawable extends Drawable implements Drawable.Callback {
     40     private Drawable mVerticalTrack;
     41     private Drawable mHorizontalTrack;
     42     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768422)
     43     private Drawable mVerticalThumb;
     44     private Drawable mHorizontalThumb;
     45 
     46     private int mRange;
     47     private int mOffset;
     48     private int mExtent;
     49 
     50     private boolean mVertical;
     51     private boolean mBoundsChanged;
     52     private boolean mRangeChanged;
     53     private boolean mAlwaysDrawHorizontalTrack;
     54     private boolean mAlwaysDrawVerticalTrack;
     55     private boolean mMutated;
     56 
     57     private int mAlpha = 255;
     58     private boolean mHasSetAlpha;
     59 
     60     private ColorFilter mColorFilter;
     61     private boolean mHasSetColorFilter;
     62 
     63     /**
     64      * Indicate whether the horizontal scrollbar track should always be drawn
     65      * regardless of the extent. Defaults to false.
     66      *
     67      * @param alwaysDrawTrack Whether the track should always be drawn
     68      *
     69      * @see #getAlwaysDrawHorizontalTrack()
     70      */
     71     public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
     72         mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
     73     }
     74 
     75     /**
     76      * Indicate whether the vertical scrollbar track should always be drawn
     77      * regardless of the extent. Defaults to false.
     78      *
     79      * @param alwaysDrawTrack Whether the track should always be drawn
     80      *
     81      * @see #getAlwaysDrawVerticalTrack()
     82      */
     83     public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
     84         mAlwaysDrawVerticalTrack = alwaysDrawTrack;
     85     }
     86 
     87     /**
     88      * @return whether the vertical scrollbar track should always be drawn
     89      *         regardless of the extent.
     90      *
     91      * @see #setAlwaysDrawVerticalTrack(boolean)
     92      */
     93     public boolean getAlwaysDrawVerticalTrack() {
     94         return mAlwaysDrawVerticalTrack;
     95     }
     96 
     97     /**
     98      * @return whether the horizontal scrollbar track should always be drawn
     99      *         regardless of the extent.
    100      *
    101      * @see #setAlwaysDrawHorizontalTrack(boolean)
    102      */
    103     public boolean getAlwaysDrawHorizontalTrack() {
    104         return mAlwaysDrawHorizontalTrack;
    105     }
    106 
    107     public void setParameters(int range, int offset, int extent, boolean vertical) {
    108         if (mVertical != vertical) {
    109             mVertical = vertical;
    110 
    111             mBoundsChanged = true;
    112         }
    113 
    114         if (mRange != range || mOffset != offset || mExtent != extent) {
    115             mRange = range;
    116             mOffset = offset;
    117             mExtent = extent;
    118 
    119             mRangeChanged = true;
    120         }
    121     }
    122 
    123     @Override
    124     public void draw(Canvas canvas) {
    125         final boolean vertical = mVertical;
    126         final int extent = mExtent;
    127         final int range = mRange;
    128 
    129         boolean drawTrack = true;
    130         boolean drawThumb = true;
    131         if (extent <= 0 || range <= extent) {
    132             drawTrack = vertical ? mAlwaysDrawVerticalTrack : mAlwaysDrawHorizontalTrack;
    133             drawThumb = false;
    134         }
    135 
    136         final Rect r = getBounds();
    137         if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) {
    138             return;
    139         }
    140 
    141         if (drawTrack) {
    142             drawTrack(canvas, r, vertical);
    143         }
    144 
    145         if (drawThumb) {
    146             final int scrollBarLength = vertical ? r.height() : r.width();
    147             final int thickness = vertical ? r.width() : r.height();
    148             final int thumbLength =
    149                     ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range);
    150             final int thumbOffset =
    151                     ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range,
    152                             mOffset);
    153 
    154             drawThumb(canvas, r, thumbOffset, thumbLength, vertical);
    155         }
    156     }
    157 
    158     @Override
    159     protected void onBoundsChange(Rect bounds) {
    160         super.onBoundsChange(bounds);
    161         mBoundsChanged = true;
    162     }
    163 
    164     @Override
    165     public boolean isStateful() {
    166         return (mVerticalTrack != null && mVerticalTrack.isStateful())
    167                 || (mVerticalThumb != null && mVerticalThumb.isStateful())
    168                 || (mHorizontalTrack != null && mHorizontalTrack.isStateful())
    169                 || (mHorizontalThumb != null && mHorizontalThumb.isStateful())
    170                 || super.isStateful();
    171     }
    172 
    173     @Override
    174     protected boolean onStateChange(int[] state) {
    175         boolean changed = super.onStateChange(state);
    176         if (mVerticalTrack != null) {
    177             changed |= mVerticalTrack.setState(state);
    178         }
    179         if (mVerticalThumb != null) {
    180             changed |= mVerticalThumb.setState(state);
    181         }
    182         if (mHorizontalTrack != null) {
    183             changed |= mHorizontalTrack.setState(state);
    184         }
    185         if (mHorizontalThumb != null) {
    186             changed |= mHorizontalThumb.setState(state);
    187         }
    188         return changed;
    189     }
    190 
    191     private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
    192         final Drawable track;
    193         if (vertical) {
    194             track = mVerticalTrack;
    195         } else {
    196             track = mHorizontalTrack;
    197         }
    198 
    199         if (track != null) {
    200             if (mBoundsChanged) {
    201                 track.setBounds(bounds);
    202             }
    203             track.draw(canvas);
    204         }
    205     }
    206 
    207     private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
    208         final boolean changed = mRangeChanged || mBoundsChanged;
    209         if (vertical) {
    210             if (mVerticalThumb != null) {
    211                 final Drawable thumb = mVerticalThumb;
    212                 if (changed) {
    213                     thumb.setBounds(bounds.left, bounds.top + offset,
    214                             bounds.right, bounds.top + offset + length);
    215                 }
    216 
    217                 thumb.draw(canvas);
    218             }
    219         } else {
    220             if (mHorizontalThumb != null) {
    221                 final Drawable thumb = mHorizontalThumb;
    222                 if (changed) {
    223                     thumb.setBounds(bounds.left + offset, bounds.top,
    224                             bounds.left + offset + length, bounds.bottom);
    225                 }
    226 
    227                 thumb.draw(canvas);
    228             }
    229         }
    230     }
    231 
    232     /**
    233      * @see android.view.View#setVerticalThumbDrawable(Drawable)
    234      */
    235     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    236     public void setVerticalThumbDrawable(Drawable thumb) {
    237         if (mVerticalThumb != null) {
    238             mVerticalThumb.setCallback(null);
    239         }
    240 
    241         propagateCurrentState(thumb);
    242         mVerticalThumb = thumb;
    243     }
    244 
    245     /**
    246      * @see View#getVerticalTrackDrawable()
    247      */
    248     public @Nullable Drawable getVerticalTrackDrawable() {
    249         return mVerticalTrack;
    250     }
    251 
    252     /**
    253      * @see View#getVerticalThumbDrawable()
    254      */
    255     public @Nullable Drawable getVerticalThumbDrawable() {
    256         return mVerticalThumb;
    257     }
    258 
    259     /**
    260      * @see View#getHorizontalTrackDrawable()
    261      */
    262     public @Nullable Drawable getHorizontalTrackDrawable() {
    263         return mHorizontalTrack;
    264     }
    265 
    266     /**
    267      * @see View#getHorizontalThumbDrawable()
    268      */
    269     public @Nullable Drawable getHorizontalThumbDrawable() {
    270         return mHorizontalThumb;
    271     }
    272 
    273     /**
    274      * @see android.view.View#setVerticalTrackDrawable(Drawable)
    275      */
    276     public void setVerticalTrackDrawable(Drawable track) {
    277         if (mVerticalTrack != null) {
    278             mVerticalTrack.setCallback(null);
    279         }
    280 
    281         propagateCurrentState(track);
    282         mVerticalTrack = track;
    283     }
    284 
    285     /**
    286      * @see android.view.View#setHorizontalThumbDrawable(Drawable)
    287      */
    288     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    289     public void setHorizontalThumbDrawable(Drawable thumb) {
    290         if (mHorizontalThumb != null) {
    291             mHorizontalThumb.setCallback(null);
    292         }
    293 
    294         propagateCurrentState(thumb);
    295         mHorizontalThumb = thumb;
    296     }
    297 
    298     public void setHorizontalTrackDrawable(Drawable track) {
    299         if (mHorizontalTrack != null) {
    300             mHorizontalTrack.setCallback(null);
    301         }
    302 
    303         propagateCurrentState(track);
    304         mHorizontalTrack = track;
    305     }
    306 
    307     private void propagateCurrentState(Drawable d) {
    308         if (d != null) {
    309             if (mMutated) {
    310                 d.mutate();
    311             }
    312 
    313             d.setState(getState());
    314             d.setCallback(this);
    315 
    316             if (mHasSetAlpha) {
    317                 d.setAlpha(mAlpha);
    318             }
    319 
    320             if (mHasSetColorFilter) {
    321                 d.setColorFilter(mColorFilter);
    322             }
    323         }
    324     }
    325 
    326     public int getSize(boolean vertical) {
    327         if (vertical) {
    328             return mVerticalTrack != null ? mVerticalTrack.getIntrinsicWidth() :
    329                     mVerticalThumb != null ? mVerticalThumb.getIntrinsicWidth() : 0;
    330         } else {
    331             return mHorizontalTrack != null ? mHorizontalTrack.getIntrinsicHeight() :
    332                     mHorizontalThumb != null ? mHorizontalThumb.getIntrinsicHeight() : 0;
    333         }
    334     }
    335 
    336     @Override
    337     public ScrollBarDrawable mutate() {
    338         if (!mMutated && super.mutate() == this) {
    339             if (mVerticalTrack != null) {
    340                 mVerticalTrack.mutate();
    341             }
    342             if (mVerticalThumb != null) {
    343                 mVerticalThumb.mutate();
    344             }
    345             if (mHorizontalTrack != null) {
    346                 mHorizontalTrack.mutate();
    347             }
    348             if (mHorizontalThumb != null) {
    349                 mHorizontalThumb.mutate();
    350             }
    351             mMutated = true;
    352         }
    353         return this;
    354     }
    355 
    356     @Override
    357     public void setAlpha(int alpha) {
    358         mAlpha = alpha;
    359         mHasSetAlpha = true;
    360 
    361         if (mVerticalTrack != null) {
    362             mVerticalTrack.setAlpha(alpha);
    363         }
    364         if (mVerticalThumb != null) {
    365             mVerticalThumb.setAlpha(alpha);
    366         }
    367         if (mHorizontalTrack != null) {
    368             mHorizontalTrack.setAlpha(alpha);
    369         }
    370         if (mHorizontalThumb != null) {
    371             mHorizontalThumb.setAlpha(alpha);
    372         }
    373     }
    374 
    375     @Override
    376     public int getAlpha() {
    377         return mAlpha;
    378     }
    379 
    380     @Override
    381     public void setColorFilter(ColorFilter colorFilter) {
    382         mColorFilter = colorFilter;
    383         mHasSetColorFilter = true;
    384 
    385         if (mVerticalTrack != null) {
    386             mVerticalTrack.setColorFilter(colorFilter);
    387         }
    388         if (mVerticalThumb != null) {
    389             mVerticalThumb.setColorFilter(colorFilter);
    390         }
    391         if (mHorizontalTrack != null) {
    392             mHorizontalTrack.setColorFilter(colorFilter);
    393         }
    394         if (mHorizontalThumb != null) {
    395             mHorizontalThumb.setColorFilter(colorFilter);
    396         }
    397     }
    398 
    399     @Override
    400     public ColorFilter getColorFilter() {
    401         return mColorFilter;
    402     }
    403 
    404     @Override
    405     public int getOpacity() {
    406         return PixelFormat.TRANSLUCENT;
    407     }
    408 
    409     @Override
    410     public void invalidateDrawable(@NonNull Drawable who) {
    411         invalidateSelf();
    412     }
    413 
    414     @Override
    415     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
    416         scheduleSelf(what, when);
    417     }
    418 
    419     @Override
    420     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
    421         unscheduleSelf(what);
    422     }
    423 
    424     @Override
    425     public String toString() {
    426         return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
    427                " extent=" + mExtent + (mVertical ? " V" : " H");
    428     }
    429 }
    430 
    431 
    432