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; 18 19 import static com.android.car.carlauncher.AppGridConstants.PageOrientation; 20 import static com.android.car.carlauncher.AppGridConstants.isHorizontal; 21 22 import android.content.Context; 23 import android.util.AttributeSet; 24 import android.view.ViewGroup; 25 import android.widget.FrameLayout; 26 27 import androidx.core.content.ContextCompat; 28 29 import com.android.car.carlauncher.pagination.PageMeasurementHelper.GridDimensions; 30 import com.android.car.carlauncher.pagination.PageMeasurementHelper.PageDimensions; 31 import com.android.car.carlauncher.pagination.PaginationController.DimensionUpdateListener; 32 33 /** 34 * A scrollbar like view that dynamically adjusts its offset and size to indicate to users which 35 * page they are on when scrolling through the app grid. 36 */ 37 public class PageIndicator extends FrameLayout implements DimensionUpdateListener { 38 private final long mAppearAnimationDurationMs; 39 private final long mAppearAnimationDelayMs; 40 private final long mFadeAnimationDurationMs; 41 private final long mFadeAnimationDelayMs; 42 @PageOrientation 43 private final int mPageOrientation; 44 45 private FrameLayout mContainer; 46 private int mAppGridWidth; 47 private int mAppGridHeight; 48 private int mAppGridOffset; 49 private int mPageCount = 1; 50 51 private float mOffsetScaleFactor = 1.f; 52 PageIndicator(Context context, AttributeSet attrs)53 public PageIndicator(Context context, AttributeSet attrs) { 54 super(context, attrs); 55 mFadeAnimationDurationMs = getResources().getInteger( 56 R.integer.ms_scrollbar_fade_animation_duration); 57 mFadeAnimationDelayMs = getResources().getInteger( 58 R.integer.ms_scrollbar_fade_animation_delay); 59 mAppearAnimationDurationMs = getResources().getInteger( 60 R.integer.ms_scrollbar_appear_animation_duration); 61 mAppearAnimationDelayMs = getResources().getInteger( 62 R.integer.ms_scrollbar_appear_animation_delay); 63 mPageOrientation = getResources().getBoolean(R.bool.use_vertical_app_grid) 64 ? PageOrientation.VERTICAL : PageOrientation.HORIZONTAL; 65 setBackground(ContextCompat.getDrawable(context, R.drawable.page_indicator_bar)); 66 setLayoutParams(new LayoutParams(/* width */ mAppGridWidth, 67 /* height */ LayoutParams.MATCH_PARENT)); 68 setAlpha(0.f); 69 } 70 setContainer(FrameLayout container)71 public void setContainer(FrameLayout container) { 72 // TODO (b/271637411): along with adding scroll controller, create an a layout xml where 73 // the container is inflated along with the scroll bar in this class so its accessible. 74 mContainer = container; 75 } 76 77 @Override onDimensionsUpdated(PageDimensions pageDimens, GridDimensions gridDimens)78 public void onDimensionsUpdated(PageDimensions pageDimens, GridDimensions gridDimens) { 79 ViewGroup.LayoutParams indicatorContainerParams = mContainer.getLayoutParams(); 80 indicatorContainerParams.width = pageDimens.pageIndicatorWidthPx; 81 indicatorContainerParams.height = pageDimens.pageIndicatorHeightPx; 82 mOffsetScaleFactor = isHorizontal(mPageOrientation) 83 ? (float) gridDimens.gridWidthPx / pageDimens.windowWidthPx 84 : (float) gridDimens.gridHeightPx / pageDimens.windowHeightPx; 85 mAppGridWidth = gridDimens.gridWidthPx; 86 mAppGridHeight = gridDimens.gridHeightPx; 87 updatePageCount(mPageCount); 88 } 89 90 /** 91 * Updates the dimensions of the scroll bar when number of pages in app grid changes. 92 */ updatePageCount(int pageCount)93 void updatePageCount(int pageCount) { 94 mPageCount = pageCount; 95 LayoutParams params = (LayoutParams) getLayoutParams(); 96 if (isHorizontal(mPageOrientation)) { 97 params.width = mAppGridWidth / mPageCount; 98 } else { 99 params.height = mAppGridHeight / mPageCount; 100 } 101 setLayoutParams(params); 102 updateOffset(mAppGridOffset); 103 } 104 105 /** 106 * Updates the offset when recyclerview has been scrolled to xOffset. 107 */ updateOffset(int appGridOffset)108 void updateOffset(int appGridOffset) { 109 mAppGridOffset = appGridOffset; 110 LayoutParams params = (LayoutParams) getLayoutParams(); 111 int offset = (int) (mAppGridOffset * mOffsetScaleFactor / mPageCount); 112 if (isHorizontal(mPageOrientation)) { 113 params.leftMargin = offset; 114 } else { 115 params.topMargin = offset; 116 } 117 setLayoutParams(params); 118 } 119 animateAppearance()120 void animateAppearance() { 121 // TODO (b/273771594) allow custom animations for fade transitions 122 animate().alpha(1.f) 123 .setDuration(mAppearAnimationDurationMs) 124 .setStartDelay(mAppearAnimationDelayMs); 125 } 126 animateFading()127 void animateFading() { 128 animate().alpha(0.f) 129 .setDuration(mFadeAnimationDelayMs) 130 .setStartDelay(mFadeAnimationDelayMs); 131 } 132 } 133