blob: 682dcc0e42c04e1a7ac51e12bdec4a2dac82e371 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.leanback.widget;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.RestrictTo;
import androidx.leanback.R;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public class ThumbsBar extends LinearLayout {
// initial value for Thumb's number before measuring the screen size
int mNumOfThumbs = -1;
int mThumbWidthInPixel;
int mThumbHeightInPixel;
int mHeroThumbWidthInPixel;
int mHeroThumbHeightInPixel;
int mMeasuredMarginInPixel;
final SparseArray<Bitmap> mBitmaps = new SparseArray<>();
// flag to determine if the number of thumbs in thumbs bar is set by user through
// setNumberofThumbs API or auto-calculated according to android tv design spec.
private boolean mIsUserSets = false;
public ThumbsBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThumbsBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// According to the spec,
// the width of non-hero thumb should be 80% of HeroThumb's Width, i.e. 0.8 * 192dp = 154dp
mThumbWidthInPixel = context.getResources().getDimensionPixelSize(
R.dimen.lb_playback_transport_thumbs_width);
mThumbHeightInPixel = context.getResources().getDimensionPixelSize(
R.dimen.lb_playback_transport_thumbs_height);
// According to the spec, the width of HeroThumb should be 192dp
mHeroThumbHeightInPixel = context.getResources().getDimensionPixelSize(
R.dimen.lb_playback_transport_hero_thumbs_width);
mHeroThumbWidthInPixel = context.getResources().getDimensionPixelSize(
R.dimen.lb_playback_transport_hero_thumbs_height);
// According to the spec, the margin between thumbs to be 4dp
mMeasuredMarginInPixel = context.getResources().getDimensionPixelSize(
R.dimen.lb_playback_transport_thumbs_margin);
}
/**
* Get hero index which is the middle child.
*/
public int getHeroIndex() {
return getChildCount() / 2;
}
/**
* Set size of thumb view in pixels
*/
public void setThumbSize(int width, int height) {
mThumbHeightInPixel = height;
mThumbWidthInPixel = width;
int heroIndex = getHeroIndex();
for (int i = 0; i < getChildCount(); i++) {
if (heroIndex != i) {
View child = getChildAt(i);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
boolean changed = false;
if (lp.height != height) {
lp.height = height;
changed = true;
}
if (lp.width != width) {
lp.width = width;
changed = true;
}
if (changed) {
child.setLayoutParams(lp);
}
}
}
}
/**
* Set size of hero thumb view in pixels, it is usually larger than other thumbs.
*/
public void setHeroThumbSize(int width, int height) {
mHeroThumbHeightInPixel = height;
mHeroThumbWidthInPixel = width;
int heroIndex = getHeroIndex();
for (int i = 0; i < getChildCount(); i++) {
if (heroIndex == i) {
View child = getChildAt(i);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
boolean changed = false;
if (lp.height != height) {
lp.height = height;
changed = true;
}
if (lp.width != width) {
lp.width = width;
changed = true;
}
if (changed) {
child.setLayoutParams(lp);
}
}
}
}
/**
* Set the space between thumbs in pixels
*/
public void setThumbSpace(int spaceInPixel) {
mMeasuredMarginInPixel = spaceInPixel;
requestLayout();
}
/**
* Set number of thumb views.
*/
public void setNumberOfThumbs(int numOfThumbs) {
mIsUserSets = true;
mNumOfThumbs = numOfThumbs;
setNumberOfThumbsInternal();
}
/**
* Helper function for setNumberOfThumbs.
* Will Update the layout settings in ThumbsBar based on mNumOfThumbs
*/
private void setNumberOfThumbsInternal() {
while (getChildCount() > mNumOfThumbs) {
removeView(getChildAt(getChildCount() - 1));
}
while (getChildCount() < mNumOfThumbs) {
View view = createThumbView(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mThumbWidthInPixel,
mThumbHeightInPixel);
addView(view, lp);
}
int heroIndex = getHeroIndex();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
if (heroIndex == i) {
lp.width = mHeroThumbWidthInPixel;
lp.height = mHeroThumbHeightInPixel;
} else {
lp.width = mThumbWidthInPixel;
lp.height = mThumbHeightInPixel;
}
child.setLayoutParams(lp);
}
}
private static int roundUp(int num, int divisor) {
return (num + divisor - 1) / divisor;
}
/**
* Helper function to compute how many thumbs should be put in the screen
* Assume we should put x's non-hero thumbs in the screen, the equation should be
* 192dp (width of hero thumbs) +
* 154dp (width of common thumbs) * x +
* 4dp (width of the margin between thumbs) * x
* = width
* So the calculated number of non-hero thumbs should be (width - 192dp) / 158dp.
* If the calculated number of non-hero thumbs is less than 2, it will be updated to 2
* or if the calculated number or non-hero thumbs is not an even number, it will be
* decremented by one.
* This processing is used to make sure the arrangement of non-hero thumbs
* in ThumbsBar is symmetrical.
* Also there should be a hero thumb in the middle of the ThumbsBar,
* the final result should be non-hero thumbs (after processing) + 1.
*
* @param widthInPixel measured width in pixel
* @return The number of thumbs
*/
private int calculateNumOfThumbs(int widthInPixel) {
int nonHeroThumbNum = roundUp(widthInPixel - mHeroThumbWidthInPixel,
mThumbWidthInPixel + mMeasuredMarginInPixel);
if (nonHeroThumbNum < 2) {
// If the calculated number of non-hero thumbs is less than 2,
// it will be updated to 2
nonHeroThumbNum = 2;
} else if ((nonHeroThumbNum & 1) != 0) {
// If the calculated number or non-hero thumbs is not an even number,
// it will be increased by one.
nonHeroThumbNum++;
}
// Count Hero Thumb to the final result
return nonHeroThumbNum + 1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
// If the number of thumbs in ThumbsBar is not set by user explicitly, it will be
// recalculated based on Android TV Design Spec
if (!mIsUserSets) {
int numOfThumbs = calculateNumOfThumbs(width);
// Set new number of thumbs when calculation result is different with current number
if (mNumOfThumbs != numOfThumbs) {
mNumOfThumbs = numOfThumbs;
setNumberOfThumbsInternal();
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int heroIndex = getHeroIndex();
View heroView = getChildAt(heroIndex);
int heroLeft = getWidth() / 2 - heroView.getMeasuredWidth() / 2;
int heroRight = getWidth() / 2 + heroView.getMeasuredWidth() / 2;
heroView.layout(heroLeft, getPaddingTop(), heroRight,
getPaddingTop() + heroView.getMeasuredHeight());
int heroCenter = getPaddingTop() + heroView.getMeasuredHeight() / 2;
for (int i = heroIndex - 1; i >= 0; i--) {
heroLeft -= mMeasuredMarginInPixel;
View child = getChildAt(i);
child.layout(heroLeft - child.getMeasuredWidth(),
heroCenter - child.getMeasuredHeight() / 2,
heroLeft,
heroCenter + child.getMeasuredHeight() / 2);
heroLeft -= child.getMeasuredWidth();
}
for (int i = heroIndex + 1; i < mNumOfThumbs; i++) {
heroRight += mMeasuredMarginInPixel;
View child = getChildAt(i);
child.layout(heroRight,
heroCenter - child.getMeasuredHeight() / 2,
heroRight + child.getMeasuredWidth(),
heroCenter + child.getMeasuredHeight() / 2);
heroRight += child.getMeasuredWidth();
}
}
/**
* Create a thumb view, it's by default a ImageView.
*/
protected View createThumbView(ViewGroup parent) {
return new ImageView(parent.getContext());
}
/**
* Clear all thumb bitmaps set on thumb views.
*/
public void clearThumbBitmaps() {
for (int i = 0; i < getChildCount(); i++) {
setThumbBitmap(i, null);
}
mBitmaps.clear();
}
/**
* Get bitmap of given child index.
*/
public Bitmap getThumbBitmap(int index) {
return mBitmaps.get(index);
}
/**
* Set thumb bitmap for a given index of child.
*/
public void setThumbBitmap(int index, Bitmap bitmap) {
mBitmaps.put(index, bitmap);
((ImageView) getChildAt(index)).setImageBitmap(bitmap);
}
}