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