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 package androidx.leanback.widget;
     17 
     18 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     19 
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.util.AttributeSet;
     23 import android.util.SparseArray;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.widget.ImageView;
     27 import android.widget.LinearLayout;
     28 
     29 import androidx.annotation.RestrictTo;
     30 import androidx.leanback.R;
     31 
     32 /**
     33  * @hide
     34  */
     35 @RestrictTo(LIBRARY_GROUP)
     36 public class ThumbsBar extends LinearLayout {
     37 
     38     // initial value for Thumb's number before measuring the screen size
     39     int mNumOfThumbs = -1;
     40     int mThumbWidthInPixel;
     41     int mThumbHeightInPixel;
     42     int mHeroThumbWidthInPixel;
     43     int mHeroThumbHeightInPixel;
     44     int mMeasuredMarginInPixel;
     45     final SparseArray<Bitmap> mBitmaps = new SparseArray<>();
     46 
     47     // flag to determine if the number of thumbs in thumbs bar is set by user through
     48     // setNumberofThumbs API or auto-calculated according to android tv design spec.
     49     private boolean mIsUserSets = false;
     50 
     51     public ThumbsBar(Context context, AttributeSet attrs) {
     52         this(context, attrs, 0);
     53     }
     54 
     55     public ThumbsBar(Context context, AttributeSet attrs, int defStyle) {
     56         super(context, attrs, defStyle);
     57         // According to the spec,
     58         // the width of non-hero thumb should be 80% of HeroThumb's Width, i.e. 0.8 * 192dp = 154dp
     59         mThumbWidthInPixel = context.getResources().getDimensionPixelSize(
     60                 R.dimen.lb_playback_transport_thumbs_width);
     61         mThumbHeightInPixel = context.getResources().getDimensionPixelSize(
     62                 R.dimen.lb_playback_transport_thumbs_height);
     63         // According to the spec, the width of HeroThumb should be 192dp
     64         mHeroThumbHeightInPixel = context.getResources().getDimensionPixelSize(
     65                 R.dimen.lb_playback_transport_hero_thumbs_width);
     66         mHeroThumbWidthInPixel = context.getResources().getDimensionPixelSize(
     67                 R.dimen.lb_playback_transport_hero_thumbs_height);
     68         // According to the spec, the margin between thumbs to be 4dp
     69         mMeasuredMarginInPixel = context.getResources().getDimensionPixelSize(
     70                 R.dimen.lb_playback_transport_thumbs_margin);
     71     }
     72 
     73     /**
     74      * Get hero index which is the middle child.
     75      */
     76     public int getHeroIndex() {
     77         return getChildCount() / 2;
     78     }
     79 
     80     /**
     81      * Set size of thumb view in pixels
     82      */
     83     public void setThumbSize(int width, int height) {
     84         mThumbHeightInPixel = height;
     85         mThumbWidthInPixel = width;
     86         int heroIndex = getHeroIndex();
     87         for (int i = 0; i < getChildCount(); i++) {
     88             if (heroIndex != i) {
     89                 View child = getChildAt(i);
     90                 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
     91                 boolean changed = false;
     92                 if (lp.height != height) {
     93                     lp.height = height;
     94                     changed = true;
     95                 }
     96                 if (lp.width != width) {
     97                     lp.width = width;
     98                     changed = true;
     99                 }
    100                 if (changed) {
    101                     child.setLayoutParams(lp);
    102                 }
    103             }
    104         }
    105     }
    106 
    107     /**
    108      * Set size of hero thumb view in pixels, it is usually larger than other thumbs.
    109      */
    110     public void setHeroThumbSize(int width, int height) {
    111         mHeroThumbHeightInPixel = height;
    112         mHeroThumbWidthInPixel = width;
    113         int heroIndex = getHeroIndex();
    114         for (int i = 0; i < getChildCount(); i++) {
    115             if (heroIndex == i) {
    116                 View child = getChildAt(i);
    117                 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
    118                 boolean changed = false;
    119                 if (lp.height != height) {
    120                     lp.height = height;
    121                     changed = true;
    122                 }
    123                 if (lp.width != width) {
    124                     lp.width = width;
    125                     changed = true;
    126                 }
    127                 if (changed) {
    128                     child.setLayoutParams(lp);
    129                 }
    130             }
    131         }
    132     }
    133 
    134     /**
    135      * Set the space between thumbs in pixels
    136      */
    137     public void setThumbSpace(int spaceInPixel) {
    138         mMeasuredMarginInPixel = spaceInPixel;
    139         requestLayout();
    140     }
    141 
    142     /**
    143      * Set number of thumb views.
    144      */
    145     public void setNumberOfThumbs(int numOfThumbs) {
    146         mIsUserSets = true;
    147         mNumOfThumbs = numOfThumbs;
    148         setNumberOfThumbsInternal();
    149     }
    150 
    151     /**
    152      * Helper function for setNumberOfThumbs.
    153      * Will Update the layout settings in ThumbsBar based on mNumOfThumbs
    154      */
    155     private void setNumberOfThumbsInternal() {
    156         while (getChildCount() > mNumOfThumbs) {
    157             removeView(getChildAt(getChildCount() - 1));
    158         }
    159         while (getChildCount() < mNumOfThumbs) {
    160             View view = createThumbView(this);
    161             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mThumbWidthInPixel,
    162                     mThumbHeightInPixel);
    163             addView(view, lp);
    164         }
    165         int heroIndex = getHeroIndex();
    166         for (int i = 0; i < getChildCount(); i++) {
    167             View child = getChildAt(i);
    168             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
    169             if (heroIndex == i) {
    170                 lp.width = mHeroThumbWidthInPixel;
    171                 lp.height = mHeroThumbHeightInPixel;
    172             } else {
    173                 lp.width = mThumbWidthInPixel;
    174                 lp.height = mThumbHeightInPixel;
    175             }
    176             child.setLayoutParams(lp);
    177         }
    178     }
    179 
    180     private static int roundUp(int num, int divisor) {
    181         return (num + divisor - 1) / divisor;
    182     }
    183 
    184     /**
    185      * Helper function to compute how many thumbs should be put in the screen
    186      * Assume we should put x's non-hero thumbs in the screen, the equation should be
    187      *   192dp (width of hero thumbs) +
    188      *   154dp (width of common thumbs) * x +
    189      *   4dp (width of the margin between thumbs) * x
    190      *     = width
    191      * So the calculated number of non-hero thumbs should be (width - 192dp) / 158dp.
    192      * If the calculated number of non-hero thumbs is less than 2, it will be updated to 2
    193      * or if the calculated number or non-hero thumbs is not an even number, it will be
    194      * decremented by one.
    195      * This processing is used to make sure the arrangement of non-hero thumbs
    196      * in ThumbsBar is symmetrical.
    197      * Also there should be a hero thumb in the middle of the ThumbsBar,
    198      * the final result should be non-hero thumbs (after processing) + 1.
    199      *
    200      * @param  widthInPixel measured width in pixel
    201      * @return The number of thumbs
    202      */
    203     private int calculateNumOfThumbs(int widthInPixel) {
    204         int nonHeroThumbNum = roundUp(widthInPixel - mHeroThumbWidthInPixel,
    205                 mThumbWidthInPixel + mMeasuredMarginInPixel);
    206         if (nonHeroThumbNum < 2) {
    207             // If the calculated number of non-hero thumbs is less than 2,
    208             // it will be updated to 2
    209             nonHeroThumbNum = 2;
    210         } else if ((nonHeroThumbNum & 1) != 0) {
    211             // If the calculated number or non-hero thumbs is not an even number,
    212             // it will be increased by one.
    213             nonHeroThumbNum++;
    214         }
    215         // Count Hero Thumb to the final result
    216         return nonHeroThumbNum + 1;
    217     }
    218 
    219     @Override
    220     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    221         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    222         int width = getMeasuredWidth();
    223         // If the number of thumbs in ThumbsBar is not set by user explicitly, it will be
    224         // recalculated based on Android TV Design Spec
    225         if (!mIsUserSets) {
    226             int numOfThumbs = calculateNumOfThumbs(width);
    227             // Set new number of thumbs when calculation result is different with current number
    228             if (mNumOfThumbs != numOfThumbs) {
    229                 mNumOfThumbs = numOfThumbs;
    230                 setNumberOfThumbsInternal();
    231             }
    232         }
    233     }
    234 
    235     @Override
    236     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    237         super.onLayout(changed, l, t, r, b);
    238         int heroIndex = getHeroIndex();
    239         View heroView = getChildAt(heroIndex);
    240         int heroLeft = getWidth() / 2 - heroView.getMeasuredWidth() / 2;
    241         int heroRight = getWidth() / 2 + heroView.getMeasuredWidth() / 2;
    242         heroView.layout(heroLeft, getPaddingTop(), heroRight,
    243                 getPaddingTop() + heroView.getMeasuredHeight());
    244         int heroCenter = getPaddingTop() + heroView.getMeasuredHeight() / 2;
    245 
    246         for (int i = heroIndex - 1; i >= 0; i--) {
    247             heroLeft -= mMeasuredMarginInPixel;
    248             View child = getChildAt(i);
    249             child.layout(heroLeft - child.getMeasuredWidth(),
    250                     heroCenter - child.getMeasuredHeight() / 2,
    251                     heroLeft,
    252                     heroCenter + child.getMeasuredHeight() / 2);
    253             heroLeft -= child.getMeasuredWidth();
    254         }
    255         for (int i = heroIndex + 1; i < mNumOfThumbs; i++) {
    256             heroRight += mMeasuredMarginInPixel;
    257             View child = getChildAt(i);
    258             child.layout(heroRight,
    259                     heroCenter - child.getMeasuredHeight() / 2,
    260                     heroRight + child.getMeasuredWidth(),
    261                     heroCenter + child.getMeasuredHeight() / 2);
    262             heroRight += child.getMeasuredWidth();
    263         }
    264     }
    265 
    266     /**
    267      * Create a thumb view, it's by default a ImageView.
    268      */
    269     protected View createThumbView(ViewGroup parent) {
    270         return new ImageView(parent.getContext());
    271     }
    272 
    273     /**
    274      * Clear all thumb bitmaps set on thumb views.
    275      */
    276     public void clearThumbBitmaps() {
    277         for (int i = 0; i < getChildCount(); i++) {
    278             setThumbBitmap(i, null);
    279         }
    280         mBitmaps.clear();
    281     }
    282 
    283 
    284     /**
    285      * Get bitmap of given child index.
    286      */
    287     public Bitmap getThumbBitmap(int index) {
    288         return mBitmaps.get(index);
    289     }
    290 
    291     /**
    292      * Set thumb bitmap for a given index of child.
    293      */
    294     public void setThumbBitmap(int index, Bitmap bitmap) {
    295         mBitmaps.put(index, bitmap);
    296         ((ImageView) getChildAt(index)).setImageBitmap(bitmap);
    297     }
    298 }
    299