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