• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ThumbsBar(Context context, AttributeSet attrs)51     public ThumbsBar(Context context, AttributeSet attrs) {
52         this(context, attrs, 0);
53     }
54 
ThumbsBar(Context context, AttributeSet attrs, int defStyle)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      */
getHeroIndex()76     public int getHeroIndex() {
77         return getChildCount() / 2;
78     }
79 
80     /**
81      * Set size of thumb view in pixels
82      */
setThumbSize(int width, int height)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      */
setHeroThumbSize(int width, int height)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      */
setThumbSpace(int spaceInPixel)137     public void setThumbSpace(int spaceInPixel) {
138         mMeasuredMarginInPixel = spaceInPixel;
139         requestLayout();
140     }
141 
142     /**
143      * Set number of thumb views.
144      */
setNumberOfThumbs(int numOfThumbs)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      */
setNumberOfThumbsInternal()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 
roundUp(int num, int divisor)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      */
calculateNumOfThumbs(int widthInPixel)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int l, int t, int r, int b)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      */
createThumbView(ViewGroup parent)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      */
clearThumbBitmaps()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      */
getThumbBitmap(int index)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      */
setThumbBitmap(int index, Bitmap bitmap)294     public void setThumbBitmap(int index, Bitmap bitmap) {
295         mBitmaps.put(index, bitmap);
296         ((ImageView) getChildAt(index)).setImageBitmap(bitmap);
297     }
298 }
299