blob: 14eb808e6a2ff16c3799cbdfc46e3fcd65739293 [file] [log] [blame]
/*
* Copyright 2019 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 com.android.car.ui.recyclerview;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.ui.R;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link LinearLayoutManager} implementation which provides a fixed scrollbar when used with
* viewholders that vary in height. This layout manager is only compatible with {@link
* CarUiListItemAdapter}.
*/
public class CarUiListItemLayoutManager extends LinearLayoutManager {
private final int mContentItemSize;
private final int mHeaderItemSize;
private List<Integer> mListItemHeights;
private CarUiListItemAdapter mAdapter;
public CarUiListItemLayoutManager(@NonNull Context context) {
super(context);
mContentItemSize = (int) context.getResources().getDimension(
R.dimen.car_ui_list_item_height);
mHeaderItemSize = (int) context.getResources().getDimension(
R.dimen.car_ui_list_item_header_height);
setSmoothScrollbarEnabled(false);
}
@Override
public void onAttachedToWindow(RecyclerView recyclerView) {
super.onAttachedToWindow(recyclerView);
populateHeightMap(recyclerView.getAdapter());
}
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
super.onAdapterChanged(oldAdapter, newAdapter);
populateHeightMap(newAdapter);
}
@Override
public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsAdded(recyclerView, positionStart, itemCount);
populateHeightMap(recyclerView.getAdapter());
}
@Override
public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart,
int itemCount) {
super.onItemsRemoved(recyclerView, positionStart, itemCount);
populateHeightMap(recyclerView.getAdapter());
}
@Override
public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart,
int itemCount) {
super.onItemsUpdated(recyclerView, positionStart, itemCount);
populateHeightMap(recyclerView.getAdapter());
}
@Override
public void onItemsChanged(@NonNull RecyclerView recyclerView) {
super.onItemsChanged(recyclerView);
populateHeightMap(recyclerView.getAdapter());
}
@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
final int count = getChildCount();
return count > 0 ? mContentItemSize * 3 : 0;
}
@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
return Math.max(mListItemHeights.get(mListItemHeights.size() - 1), 0);
}
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
if (getChildCount() <= 0) {
return 0;
}
int firstPos = findFirstVisibleItemPosition();
if (firstPos == RecyclerView.NO_POSITION) {
return 0;
}
View view = findViewByPosition(firstPos);
if (view == null) {
return 0;
}
final int top = getDecoratedTop(view);
final int height = getDecoratedMeasuredHeight(view);
int heightOfScreen;
if (height <= 0) {
heightOfScreen = 0;
} else {
CarUiListItem item = mAdapter.getItems().get(firstPos);
if (item instanceof CarUiContentListItem) {
heightOfScreen = Math.abs(mContentItemSize * top / height);
} else if (item instanceof CarUiHeaderListItem) {
heightOfScreen = Math.abs(mHeaderItemSize * top / height);
} else {
throw new IllegalStateException("Unknown list item type.");
}
}
return mListItemHeights.get(firstPos) + heightOfScreen;
}
/**
* Populates an internal list of cumulative heights at each position for the list to be laid out
* for the adapter parameter.
*
* @param adapter the action type for the item.
*/
private void populateHeightMap(RecyclerView.Adapter adapter) {
if (!(adapter instanceof CarUiListItemAdapter)) {
throw new IllegalStateException(
"Cannot use CarUiListItemLayoutManager with an adapter that is not of type "
+ "CarUiListItemAdapter");
}
mAdapter = (CarUiListItemAdapter) adapter;
List<? extends CarUiListItem> itemList = mAdapter.getItems();
mListItemHeights = new ArrayList<>();
int cumulativeHeight = 0;
mListItemHeights.add(cumulativeHeight);
for (CarUiListItem item : itemList) {
if (item instanceof CarUiContentListItem) {
cumulativeHeight += mContentItemSize;
mListItemHeights.add(cumulativeHeight);
} else if (item instanceof CarUiHeaderListItem) {
cumulativeHeight += mHeaderItemSize;
mListItemHeights.add(cumulativeHeight);
} else {
throw new IllegalStateException("Unknown CarUiListItem type");
}
}
}
}