1 /* 2 * Copyright (C) 2023 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.car.carlauncher.pagination; 18 19 import android.view.View; 20 21 import com.android.car.carlauncher.AppGridConstants; 22 import com.android.car.carlauncher.AppGridConstants.PageOrientation; 23 import com.android.car.carlauncher.R; 24 import com.android.car.carlauncher.recyclerview.PageMarginDecoration; 25 26 /** 27 * Helper class for PaginationController that computes the measurements of app grid and app items. 28 */ 29 public class PageMeasurementHelper { 30 private final int mNumOfCols; 31 private final int mNumOfRows; 32 @PageOrientation 33 private final int mPageOrientation; 34 private final boolean mUseDefinedDimensions; 35 private final int mDefinedWidth; 36 private final int mDefinedHeight; 37 private final int mDefinedMarginHorizontal; 38 private final int mDefinedMarginVertical; 39 private final int mDefinedPageIndicatorSize; 40 41 private int mWindowWidth; 42 private int mWindowHeight; 43 private GridDimensions mGridDimensions; 44 private PageDimensions mPageDimensions; 45 PageMeasurementHelper(View windowBackground)46 public PageMeasurementHelper(View windowBackground) { 47 mNumOfCols = windowBackground.getResources().getInteger( 48 R.integer.car_app_selector_column_number); 49 mNumOfRows = windowBackground.getResources().getInteger( 50 R.integer.car_app_selector_row_number); 51 mPageOrientation = windowBackground.getResources().getBoolean(R.bool.use_vertical_app_grid) 52 ? PageOrientation.VERTICAL : PageOrientation.HORIZONTAL; 53 mUseDefinedDimensions = windowBackground.getResources().getBoolean( 54 R.bool.use_defined_app_grid_dimensions); 55 mDefinedWidth = windowBackground.getResources().getDimensionPixelSize( 56 R.dimen.app_grid_width); 57 mDefinedHeight = windowBackground.getResources().getDimensionPixelSize( 58 R.dimen.app_grid_height); 59 mDefinedMarginHorizontal = windowBackground.getResources().getDimensionPixelSize( 60 R.dimen.app_grid_margin_horizontal); 61 mDefinedMarginVertical = windowBackground.getResources().getDimensionPixelSize( 62 R.dimen.app_grid_margin_vertical); 63 mDefinedPageIndicatorSize = windowBackground.getResources().getDimensionPixelSize( 64 R.dimen.page_indicator_height); 65 } 66 67 /** 68 * @return the most recently updated app grid dimension, or {@code null} if 69 * {@link PageMeasurementHelper#handleWindowSizeChange} was never called. 70 */ getGridDimensions()71 public GridDimensions getGridDimensions() { 72 return mGridDimensions; 73 } 74 75 /** 76 * @return the most recently updated page margin decoration or {@code null} if 77 * {@link PageMeasurementHelper#handleWindowSizeChange} was never called. 78 */ getPageDimensions()79 public PageDimensions getPageDimensions() { 80 return mPageDimensions; 81 } 82 83 /** 84 * Handles window dimension change by calculating spaces available for the app grid. Returns 85 * {@code true} if the new measurements is different, and {@code false} otherwise. 86 * 87 * If dimensions has changed, it is the caller's responsibility to retrieve the page and grid 88 * dimensions and update their respective layout params. {@link PageMarginDecoration} should 89 * also be recreated and reattached to redraw the page margins. 90 * 91 * @param windowWidth width available for app grid in px. 92 * @param windowHeight height available for app grid to fill in px. 93 * @return true if the 94 */ handleWindowSizeChange(int windowWidth, int windowHeight)95 public boolean handleWindowSizeChange(int windowWidth, int windowHeight) { 96 if (mUseDefinedDimensions) { 97 windowWidth = mDefinedWidth; 98 windowHeight = mDefinedHeight; 99 } 100 boolean consumed = windowWidth != mWindowWidth || windowHeight != mWindowHeight; 101 if (consumed) { 102 mWindowWidth = windowWidth; 103 mWindowHeight = windowHeight; 104 // Step 1: calculate the width and height available to for the grid layout by accounting 105 // for spaces required to place page indicator and page margins. 106 int gridWidth = windowWidth - mDefinedMarginHorizontal * 2 107 - (isHorizontal() ? 0 : mDefinedPageIndicatorSize); 108 int gridHeight = windowHeight - mDefinedMarginVertical * 2 109 - (isHorizontal() ? mDefinedPageIndicatorSize : 0); 110 111 // Step 2: Round the measurements to ensure child view holder cells have an exact fit. 112 gridWidth = roundDownToModuloMultiple(gridWidth, mNumOfCols); 113 gridHeight = roundDownToModuloMultiple(gridHeight, mNumOfRows); 114 int cellWidth = gridWidth / mNumOfCols; 115 int cellHeight = gridHeight / mNumOfRows; 116 mGridDimensions = new GridDimensions(gridWidth, gridHeight, cellWidth, cellHeight); 117 118 // Step 3: Since the grid dimens are rounded, we need to recalculate the margins. 119 int marginHorizontal = (windowWidth - gridWidth) / 2; 120 int marginVertical = (windowHeight - gridHeight) / 2; 121 122 // Step 4: Calculate RecyclerView and PageIndicator dimens for layout params. 123 int recyclerViewWidth, recyclerViewHeight; 124 int pageIndicatorWidth, pageIndicatorHeight; 125 if (isHorizontal()) { 126 // horizontal app grid should have HORIZONTAL page indicator bar and the 127 // recyclerview width should span the entire window to not clip off the page margin 128 recyclerViewWidth = windowWidth; 129 recyclerViewHeight = gridHeight; 130 pageIndicatorWidth = gridWidth; 131 pageIndicatorHeight = mDefinedPageIndicatorSize; 132 } else { 133 // vertical app grid should have VERTICAL page indicator bar and the 134 // recyclerview height should span the entire window to not clip off the page margin 135 recyclerViewWidth = gridWidth; 136 recyclerViewHeight = windowHeight; 137 pageIndicatorWidth = mDefinedPageIndicatorSize; 138 pageIndicatorHeight = gridHeight; 139 } 140 mPageDimensions = new PageDimensions(recyclerViewWidth, recyclerViewHeight, 141 marginHorizontal, marginVertical, pageIndicatorWidth, pageIndicatorHeight, 142 windowWidth, windowHeight); 143 } 144 return consumed; 145 } 146 isHorizontal()147 private boolean isHorizontal() { 148 return AppGridConstants.isHorizontal(mPageOrientation); 149 } 150 151 /** 152 * Rounds down to the nearest modulo multiple. For example, when {@code input} is 1024 and 153 * {@code modulo} is 5, we want to round down to 1020, since 1020 is the largest number 154 * such that {1020 % 5 = 0}. 155 */ roundDownToModuloMultiple(int input, int modulo)156 private int roundDownToModuloMultiple(int input, int modulo) { 157 return input / modulo * modulo; 158 } 159 160 /** 161 * Data structure representing dimensions of the app grid. 162 * 163 * {@link GridDimensions#cellWidthPx} and {@link GridDimensions#cellHeightPx}: 164 * The width and height of each app item cell (view holder layout). 165 * 166 * {@link GridDimensions#gridWidthPx} and {@link GridDimensions#gridWidthPx}: 167 * The width and height of the app grid. These values should be equal to or less than the 168 * RecyclerView dimensions, with equal case being page margin size being 0 px. 169 */ 170 public static class GridDimensions { 171 public int gridWidthPx; 172 public int gridHeightPx; 173 public int cellWidthPx; 174 public int cellHeightPx; 175 GridDimensions(int gridWidth, int gridHeight, int cellWidth, int cellHeight)176 public GridDimensions(int gridWidth, int gridHeight, int cellWidth, int cellHeight) { 177 gridWidthPx = gridWidth; 178 gridHeightPx = gridHeight; 179 cellWidthPx = cellWidth; 180 cellHeightPx = cellHeight; 181 } 182 183 @Override toString()184 public String toString() { 185 return "%s {".formatted(super.toString()) 186 + " gridWidthPx: %d".formatted(gridWidthPx) 187 + " gridHeightPx: %d".formatted(gridHeightPx) 188 + " cellWidthPx: %d".formatted(cellWidthPx) 189 + " cellHeightPx: %d".formatted(cellHeightPx) 190 + "}"; 191 } 192 } 193 194 /** 195 * Data structure representing dimensions of the app grid. 196 * 197 * {@link PageDimensions#recyclerViewWidthPx} and {@link PageDimensions#recyclerViewHeightPx} 198 * The width and height of recycler view layout params. 199 * 200 * {@link PageDimensions#marginHorizontalPx} and {@link PageDimensions#marginVerticalPx} 201 * The margins on the left/right and top/bottom of the recycler view, respectively. 202 * 203 * {@link PageDimensions#pageIndicatorWidthPx} and {@link PageDimensions#pageIndicatorHeightPx} 204 * The width and height of the page indicator prior to resizing and adjusting offsets. 205 */ 206 public static class PageDimensions { 207 public int recyclerViewWidthPx; 208 public int recyclerViewHeightPx; 209 public int marginHorizontalPx; 210 public int marginVerticalPx; 211 public int pageIndicatorWidthPx; 212 public int pageIndicatorHeightPx; 213 public int windowWidthPx; 214 public int windowHeightPx; 215 PageDimensions(int recyclerViewWidth, int recyclerViewHeight, int marginHorizontal, int marginVertical, int pageIndicatorWidth, int pageIndicatorHeight, int windowWidth, int windowHeight)216 public PageDimensions(int recyclerViewWidth, int recyclerViewHeight, int marginHorizontal, 217 int marginVertical, int pageIndicatorWidth, int pageIndicatorHeight, 218 int windowWidth, int windowHeight) { 219 recyclerViewWidthPx = recyclerViewWidth; 220 recyclerViewHeightPx = recyclerViewHeight; 221 marginHorizontalPx = marginHorizontal; 222 marginVerticalPx = marginVertical; 223 pageIndicatorWidthPx = pageIndicatorWidth; 224 pageIndicatorHeightPx = pageIndicatorHeight; 225 windowWidthPx = windowWidth; 226 windowHeightPx = windowHeight; 227 } 228 229 @Override toString()230 public String toString() { 231 return "%s {".formatted(super.toString()) 232 + " recyclerViewWidthPx: %d".formatted(recyclerViewWidthPx) 233 + " recyclerViewHeightPx: %d".formatted(recyclerViewHeightPx) 234 + " marginHorizontalPx: %d".formatted(marginHorizontalPx) 235 + " marginVerticalPx: %d".formatted(marginVerticalPx) 236 + " pageIndicatorWidthPx: %d".formatted(pageIndicatorWidthPx) 237 + " pageIndicatorHeightPx: %d".formatted(pageIndicatorHeightPx) 238 + " windowWidthPx: %d".formatted(windowWidthPx) 239 + " windowHeightPx: %d".formatted(windowHeightPx) 240 + "}"; 241 } 242 } 243 } 244