• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 com.android.tv.twopanelsettings.slices.compat.widget;
18 
19 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.ICON_IMAGE;
20 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.RAW_IMAGE_LARGE;
21 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.UNKNOWN_IMAGE;
22 import static com.android.tv.twopanelsettings.slices.compat.widget.SliceViewPolicy.MODE_LARGE;
23 import static com.android.tv.twopanelsettings.slices.compat.widget.SliceViewPolicy.MODE_SMALL;
24 
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.util.AttributeSet;
29 import android.util.SparseArray;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import com.android.tv.twopanelsettings.R;
33 import com.android.tv.twopanelsettings.slices.compat.SliceItem;
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /** Holds style information shared between child views of a slice */
38 // @RestrictTo(RestrictTo.Scope.LIBRARY)
39 // @Deprecated // Supported for TV
40 public class SliceStyle {
41   private int mTintColor = -1;
42   private final int mTitleColor;
43   private final int mSubtitleColor;
44   private final int mHeaderTitleSize;
45   private final int mHeaderSubtitleSize;
46   private final int mVerticalHeaderTextPadding;
47   private final int mTitleSize;
48   private final int mSubtitleSize;
49   private final int mVerticalTextPadding;
50   private final int mGridTitleSize;
51   private final int mGridSubtitleSize;
52   private final int mVerticalGridTextPadding;
53   private final int mGridTopPadding;
54   private final int mGridBottomPadding;
55 
56   private final int mRowMaxHeight;
57   private final int mRowTextWithRangeHeight;
58   private final int mRowSingleTextWithRangeHeight;
59   private final int mRowMinHeight;
60   private final int mRowRangeHeight;
61   private final int mRowSelectionHeight;
62   private final int mRowTextWithSelectionHeight;
63   private final int mRowSingleTextWithSelectionHeight;
64   private final int mRowInlineRangeHeight;
65 
66   private final int mGridBigPicMinHeight;
67   private final int mGridBigPicMaxHeight;
68   private final int mGridAllImagesHeight;
69   private final int mGridImageTextHeight;
70   private final int mGridRawImageTextHeight;
71   private final int mGridMaxHeight;
72   private final int mGridMinHeight;
73 
74   private final int mListMinScrollHeight;
75   private final int mListLargeHeight;
76 
77   private final boolean mExpandToAvailableHeight;
78   private final boolean mHideHeaderRow;
79 
80   private final int mDefaultRowStyleRes;
81   private final SparseArray<RowStyle> mResourceToRowStyle = new SparseArray<>();
82   private RowStyleFactory mRowStyleFactory;
83 
84   private final Context mContext;
85 
86   private final float mImageCornerRadius;
87 
SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)88   public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
89     TypedArray a =
90         context
91             .getTheme()
92             .obtainStyledAttributes(attrs, R.styleable.SliceView, defStyleAttr, defStyleRes);
93     try {
94       int themeColor = a.getColor(R.styleable.SliceView_tintColor, -1);
95       mTintColor = themeColor != -1 ? themeColor : mTintColor;
96       mTitleColor = a.getColor(R.styleable.SliceView_titleColor, 0);
97       mSubtitleColor = a.getColor(R.styleable.SliceView_subtitleColor, 0);
98 
99       mHeaderTitleSize = (int) a.getDimension(R.styleable.SliceView_headerTitleSize, 0);
100       mHeaderSubtitleSize = (int) a.getDimension(R.styleable.SliceView_headerSubtitleSize, 0);
101       mVerticalHeaderTextPadding =
102           (int) a.getDimension(R.styleable.SliceView_headerTextVerticalPadding, 0);
103 
104       mTitleSize = (int) a.getDimension(R.styleable.SliceView_titleSize, 0);
105       mSubtitleSize = (int) a.getDimension(R.styleable.SliceView_subtitleSize, 0);
106       mVerticalTextPadding = (int) a.getDimension(R.styleable.SliceView_textVerticalPadding, 0);
107 
108       mGridTitleSize = (int) a.getDimension(R.styleable.SliceView_gridTitleSize, 0);
109       mGridSubtitleSize = (int) a.getDimension(R.styleable.SliceView_gridSubtitleSize, 0);
110       int defaultVerticalGridPadding =
111           context.getResources().getDimensionPixelSize(R.dimen.abc_slice_grid_text_inner_padding);
112       mVerticalGridTextPadding =
113           (int)
114               a.getDimension(
115                   R.styleable.SliceView_gridTextVerticalPadding, defaultVerticalGridPadding);
116       mGridTopPadding = (int) a.getDimension(R.styleable.SliceView_gridTopPadding, 0);
117       mGridBottomPadding = (int) a.getDimension(R.styleable.SliceView_gridBottomPadding, 0);
118 
119       mDefaultRowStyleRes = a.getResourceId(R.styleable.SliceView_rowStyle, 0);
120 
121       int defaultRowMinHeight =
122           context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
123       mRowMinHeight = (int) a.getDimension(R.styleable.SliceView_rowMinHeight, defaultRowMinHeight);
124 
125       int defaultRowMaxHeight =
126           context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_max_height);
127       mRowMaxHeight = (int) a.getDimension(R.styleable.SliceView_rowMaxHeight, defaultRowMaxHeight);
128 
129       int defaultRowRangeHeight =
130           context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_range_height);
131       mRowRangeHeight =
132           (int) a.getDimension(R.styleable.SliceView_rowRangeHeight, defaultRowRangeHeight);
133 
134       int defaultRowSingleTextWithRangeHeight =
135           context
136               .getResources()
137               .getDimensionPixelSize(R.dimen.abc_slice_row_range_single_text_height);
138       mRowSingleTextWithRangeHeight =
139           (int)
140               a.getDimension(
141                   R.styleable.SliceView_rowRangeSingleTextHeight,
142                   defaultRowSingleTextWithRangeHeight);
143 
144       int defaultRowInlineRangeHeight =
145           context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_range_inline_height);
146       mRowInlineRangeHeight =
147           (int)
148               a.getDimension(
149                   R.styleable.SliceView_rowInlineRangeHeight, defaultRowInlineRangeHeight);
150 
151       mExpandToAvailableHeight = a.getBoolean(R.styleable.SliceView_expandToAvailableHeight, false);
152 
153       mHideHeaderRow = a.getBoolean(R.styleable.SliceView_hideHeaderRow, false);
154 
155       mContext = context;
156 
157       mImageCornerRadius = a.getDimension(R.styleable.SliceView_imageCornerRadius, 0);
158     } finally {
159       a.recycle();
160     }
161 
162     // Note: The above colors and dimensions are styleable, but the below ones are not.
163 
164     final Resources r = context.getResources();
165 
166     mRowTextWithRangeHeight =
167         r.getDimensionPixelSize(R.dimen.abc_slice_row_range_multi_text_height);
168     mRowSelectionHeight = r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_height);
169     mRowTextWithSelectionHeight =
170         r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_multi_text_height);
171     mRowSingleTextWithSelectionHeight =
172         r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_single_text_height);
173 
174     mGridBigPicMinHeight = r.getDimensionPixelSize(R.dimen.abc_slice_big_pic_min_height);
175     mGridBigPicMaxHeight = r.getDimensionPixelSize(R.dimen.abc_slice_big_pic_max_height);
176     mGridAllImagesHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
177     mGridImageTextHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_image_text_height);
178     mGridRawImageTextHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_raw_image_text_offset);
179     mGridMinHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_min_height);
180     mGridMaxHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_max_height);
181 
182     mListMinScrollHeight = r.getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
183     mListLargeHeight = r.getDimensionPixelSize(R.dimen.abc_slice_large_height_internal);
184   }
185 
getRowMinHeight()186   public int getRowMinHeight() {
187     return mRowMinHeight;
188   }
189 
getRowMaxHeight()190   public int getRowMaxHeight() {
191     return mRowMaxHeight;
192   }
193 
getRowInlineRangeHeight()194   public int getRowInlineRangeHeight() {
195     return mRowInlineRangeHeight;
196   }
197 
setTintColor(int tint)198   public void setTintColor(int tint) {
199     mTintColor = tint;
200   }
201 
getTintColor()202   public int getTintColor() {
203     return mTintColor;
204   }
205 
getTitleColor()206   public int getTitleColor() {
207     return mTitleColor;
208   }
209 
getSubtitleColor()210   public int getSubtitleColor() {
211     return mSubtitleColor;
212   }
213 
getHeaderTitleSize()214   public int getHeaderTitleSize() {
215     return mHeaderTitleSize;
216   }
217 
getHeaderSubtitleSize()218   public int getHeaderSubtitleSize() {
219     return mHeaderSubtitleSize;
220   }
221 
getVerticalHeaderTextPadding()222   public int getVerticalHeaderTextPadding() {
223     return mVerticalHeaderTextPadding;
224   }
225 
getTitleSize()226   public int getTitleSize() {
227     return mTitleSize;
228   }
229 
getSubtitleSize()230   public int getSubtitleSize() {
231     return mSubtitleSize;
232   }
233 
getVerticalTextPadding()234   public int getVerticalTextPadding() {
235     return mVerticalTextPadding;
236   }
237 
getGridTitleSize()238   public int getGridTitleSize() {
239     return mGridTitleSize;
240   }
241 
getGridSubtitleSize()242   public int getGridSubtitleSize() {
243     return mGridSubtitleSize;
244   }
245 
getVerticalGridTextPadding()246   public int getVerticalGridTextPadding() {
247     return mVerticalGridTextPadding;
248   }
249 
getGridTopPadding()250   public int getGridTopPadding() {
251     return mGridTopPadding;
252   }
253 
getGridBottomPadding()254   public int getGridBottomPadding() {
255     return mGridBottomPadding;
256   }
257 
258   /** Returns the {@link RowStyle} to use for the given {@link SliceItem}. */
259   @NonNull
getRowStyle(@ullable SliceItem sliceItem)260   public RowStyle getRowStyle(@Nullable SliceItem sliceItem) {
261     int rowStyleRes = mDefaultRowStyleRes;
262 
263     if (sliceItem != null && mRowStyleFactory != null) {
264       int maybeStyleRes = mRowStyleFactory.getRowStyleRes(sliceItem);
265       if (maybeStyleRes != 0) {
266         rowStyleRes = maybeStyleRes;
267       }
268     }
269 
270     if (rowStyleRes == 0) {
271       // Return default values.
272       return new RowStyle(mContext, this);
273     }
274 
275     RowStyle rowStyle = mResourceToRowStyle.get(rowStyleRes);
276     if (rowStyle == null) {
277       rowStyle = new RowStyle(mContext, rowStyleRes, this);
278       mResourceToRowStyle.put(rowStyleRes, rowStyle);
279     }
280     return rowStyle;
281   }
282 
283   /** Sets the {@link RowStyleFactory} which allows multiple children to have different styles. */
setRowStyleFactory(@ullable RowStyleFactory rowStyleFactory)284   public void setRowStyleFactory(@Nullable RowStyleFactory rowStyleFactory) {
285     mRowStyleFactory = rowStyleFactory;
286   }
287 
getRowRangeHeight()288   public int getRowRangeHeight() {
289     return mRowRangeHeight;
290   }
291 
getRowSelectionHeight()292   public int getRowSelectionHeight() {
293     return mRowSelectionHeight;
294   }
295 
getExpandToAvailableHeight()296   public boolean getExpandToAvailableHeight() {
297     return mExpandToAvailableHeight;
298   }
299 
getHideHeaderRow()300   public boolean getHideHeaderRow() {
301     return mHideHeaderRow;
302   }
303 
getApplyCornerRadiusToLargeImages()304   public boolean getApplyCornerRadiusToLargeImages() {
305     return mImageCornerRadius > 0;
306   }
307 
getImageCornerRadius()308   public float getImageCornerRadius() {
309     return mImageCornerRadius;
310   }
311 
getRowHeight(RowContent row, SliceViewPolicy policy)312   public int getRowHeight(RowContent row, SliceViewPolicy policy) {
313     int maxHeight = policy.getMaxSmallHeight() > 0 ? policy.getMaxSmallHeight() : mRowMaxHeight;
314 
315     if (row.getRange() == null && row.getSelection() == null && policy.getMode() != MODE_LARGE) {
316       return maxHeight;
317     }
318 
319     if (row.getRange() != null) {
320       // If no StartItem, keep to use original layout.
321       if (row.getStartItem() == null) {
322         // Range element always has set height and then the height of the text
323         // area on the row will vary depending on 0,1 or 2 lines of text.
324         int textAreaHeight =
325             row.getLineCount() == 0
326                 ? 0
327                 : (row.getLineCount() > 1
328                     ? mRowTextWithRangeHeight
329                     : mRowSingleTextWithRangeHeight);
330         return textAreaHeight + mRowRangeHeight;
331       } else {
332         // If has StartItem then Range element is inline, the row height should be more to
333         // fit thumb ripple.
334         return mRowInlineRangeHeight;
335       }
336     }
337 
338     if (row.getSelection() != null) {
339       // Selection element always has set height and then the height of the text
340       // area on the row will vary depending on if 1 or 2 lines of text.
341       int textAreaHeight =
342           row.getLineCount() > 1 ? mRowTextWithSelectionHeight : mRowSingleTextWithSelectionHeight;
343       return textAreaHeight + mRowSelectionHeight;
344     }
345 
346     return (row.getLineCount() > 1 || row.getIsHeader()) ? maxHeight : mRowMinHeight;
347   }
348 
getGridHeight(GridContent grid, SliceViewPolicy policy)349   public int getGridHeight(GridContent grid, SliceViewPolicy policy) {
350     boolean isSmall = policy.getMode() == MODE_SMALL;
351     if (!grid.isValid()) {
352       return 0;
353     }
354     int largestImageMode = grid.getLargestImageMode();
355     int height;
356     if (grid.isAllImages()) {
357       height =
358           (grid.getGridContent().size() == 1)
359               ? (isSmall ? mGridBigPicMinHeight : mGridBigPicMaxHeight)
360               : (largestImageMode == ICON_IMAGE
361                   ? mGridMinHeight
362                   : (largestImageMode == RAW_IMAGE_LARGE
363                       ? grid.getFirstImageSize(mContext).y
364                       : mGridAllImagesHeight));
365     } else {
366       boolean twoLines = grid.getMaxCellLineCount() > 1;
367       boolean hasImage = grid.hasImage();
368       boolean iconImagesOrNone =
369           largestImageMode == ICON_IMAGE || largestImageMode == UNKNOWN_IMAGE;
370       height =
371           largestImageMode == RAW_IMAGE_LARGE
372               ? (grid.getFirstImageSize(mContext).y + (twoLines ? 2 : 1) * mGridRawImageTextHeight)
373               : (twoLines && !isSmall)
374                   ? (hasImage ? mGridMaxHeight : mGridMinHeight)
375                   : (iconImagesOrNone ? mGridMinHeight : mGridImageTextHeight);
376     }
377     int topPadding = grid.isAllImages() && grid.getRowIndex() == 0 ? mGridTopPadding : 0;
378     int bottomPadding = grid.isAllImages() && grid.getIsLastIndex() ? mGridBottomPadding : 0;
379     return height + topPadding + bottomPadding;
380   }
381 
getListHeight(ListContent list, SliceViewPolicy policy)382   public int getListHeight(ListContent list, SliceViewPolicy policy) {
383     if (policy.getMode() == MODE_SMALL) {
384       return list.getHeader().getHeight(this, policy);
385     }
386     int maxHeight = policy.getMaxHeight();
387     boolean scrollable = policy.isScrollable();
388 
389     int desiredHeight = getListItemsHeight(list.getRowItems(), policy);
390     if (maxHeight > 0) {
391       // Always ensure we're at least the height of our small version.
392       int smallHeight = list.getHeader().getHeight(this, policy);
393       maxHeight = Math.max(smallHeight, maxHeight);
394     }
395     int maxLargeHeight = maxHeight > 0 ? maxHeight : mListLargeHeight;
396     // Do we have enough content to reasonably scroll in our max?
397     boolean bigEnoughToScroll = desiredHeight - maxLargeHeight >= mListMinScrollHeight;
398 
399     // Adjust for scrolling
400     int height =
401         bigEnoughToScroll && !getExpandToAvailableHeight()
402             ? maxLargeHeight
403             : maxHeight <= 0 ? desiredHeight : Math.min(maxLargeHeight, desiredHeight);
404     if (!scrollable) {
405       height =
406           getListItemsHeight(
407               getListItemsForNonScrollingList(list, height, policy).getDisplayedItems(), policy);
408     }
409     return height;
410   }
411 
getListItemsHeight(List<SliceContent> listItems, SliceViewPolicy policy)412   public int getListItemsHeight(List<SliceContent> listItems, SliceViewPolicy policy) {
413     if (listItems == null) {
414       return 0;
415     }
416 
417     int height = 0;
418     for (int i = 0; i < listItems.size(); i++) {
419       SliceContent listItem = listItems.get(i);
420       if (i == 0 && shouldSkipFirstListItem(listItems)) {
421         continue;
422       }
423       height += listItem.getHeight(this, policy);
424     }
425     return height;
426   }
427 
428   /**
429    * Returns a list of items that can fit in the provided height. If this list has a see more item
430    * this will be displayed in the list if appropriate.
431    *
432    * @param list the list from which to source the items.
433    * @param availableHeight to use to determine the row items to return.
434    * @param policy the policy info (scrolling, mode) to use when determining row items to return.
435    * @return the list of items that can be displayed in the provided height.
436    */
437   @NonNull
getListItemsForNonScrollingList( ListContent list, int availableHeight, SliceViewPolicy policy)438   public DisplayedListItems getListItemsForNonScrollingList(
439       ListContent list, int availableHeight, SliceViewPolicy policy) {
440     ArrayList<SliceContent> visibleItems = new ArrayList<>();
441     int hiddenItemCount = 0;
442     if (list.getRowItems() == null || list.getRowItems().isEmpty()) {
443       return new DisplayedListItems(visibleItems, hiddenItemCount);
444     }
445 
446     final boolean skipFirstItem = shouldSkipFirstListItem(list.getRowItems());
447 
448     int visibleHeight = 0;
449     final int rowCount = list.getRowItems().size();
450     for (int i = 0; i < rowCount; i++) {
451       SliceContent listItem = list.getRowItems().get(i);
452       if (i == 0 && skipFirstItem) {
453         continue;
454       }
455       int itemHeight = listItem.getHeight(this, policy);
456       if (availableHeight > 0 && visibleHeight + itemHeight > availableHeight) {
457         hiddenItemCount = rowCount - i;
458         break;
459       } else {
460         visibleHeight += itemHeight;
461         visibleItems.add(listItem);
462       }
463     }
464 
465     // Only add see more if we're at least showing one item and it's not the header.
466     final int minItemCountForSeeMore = skipFirstItem ? 1 : 2;
467     if (list.getSeeMoreItem() != null
468         && visibleItems.size() >= minItemCountForSeeMore
469         && hiddenItemCount > 0) {
470       // Need to show see more
471       int seeMoreHeight = list.getSeeMoreItem().getHeight(this, policy);
472       visibleHeight += seeMoreHeight;
473 
474       // Free enough vertical space to fit the see more item.
475       while (visibleHeight > availableHeight && visibleItems.size() >= minItemCountForSeeMore) {
476         int lastIndex = visibleItems.size() - 1;
477         SliceContent lastItem = visibleItems.get(lastIndex);
478         visibleHeight -= lastItem.getHeight(this, policy);
479         visibleItems.remove(lastIndex);
480         hiddenItemCount++;
481       }
482 
483       if (visibleItems.size() >= minItemCountForSeeMore) {
484         visibleItems.add(list.getSeeMoreItem());
485       } else {
486         // Not possible to free enough vertical space. We'll show only the header.
487         visibleHeight -= seeMoreHeight;
488       }
489     }
490     if (visibleItems.isEmpty()) {
491       // Didn't have enough space to show anything; should still show something
492       visibleItems.add(list.getRowItems().get(0));
493     }
494     return new DisplayedListItems(visibleItems, hiddenItemCount);
495   }
496 
497   /**
498    * Returns a list of items that should be displayed to the user.
499    *
500    * @param list the list from which to source the items.
501    */
502   @NonNull
getListItemsToDisplay(@onNull ListContent list)503   public List<SliceContent> getListItemsToDisplay(@NonNull ListContent list) {
504     List<SliceContent> rowItems = list.getRowItems();
505     if (!rowItems.isEmpty() && shouldSkipFirstListItem(rowItems)) {
506       return rowItems.subList(1, rowItems.size());
507     }
508     return rowItems;
509   }
510 
511   /** Returns true if the first item of a list should be skipped. */
shouldSkipFirstListItem(List<SliceContent> rowItems)512   private boolean shouldSkipFirstListItem(List<SliceContent> rowItems) {
513     // Hide header row if requested, but only if there is at least one non-header row.
514     return getHideHeaderRow()
515         && rowItems.size() > 1
516         && rowItems.get(0) instanceof RowContent
517         && ((RowContent) rowItems.get(0)).getIsHeader();
518   }
519 }
520