blob: dd7bb863abd16e0fc92f66f1e0ffdeeb1cbd8e99 [file] [log] [blame]
/*
* Copyright (C) 2021 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 static com.android.car.ui.core.CarUi.MIN_TARGET_API;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
import androidx.recyclerview.widget.RecyclerView.OnFlingListener;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.LayoutStyleOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.OnChildAttachStateChangeListenerOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.OnScrollListenerOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
import com.android.car.ui.preference.PreferenceFragment.AndroidxRecyclerViewProvider;
import com.android.car.ui.recyclerview.RecyclerViewAdapterAdapterV1.ViewHolderAdapterV1;
import java.util.ArrayList;
import java.util.List;
/**
* AdapterV1 class for making oem implementation available for UI
* <p>
* For CarUi internal usage only.
*/
@TargetApi(MIN_TARGET_API)
public final class RecyclerViewAdapterV1 extends FrameLayout
implements CarUiRecyclerView, OnScrollListenerOEMV1, AndroidxRecyclerViewProvider,
OnChildAttachStateChangeListenerOEMV1 {
@Nullable
private RecyclerViewOEMV1 mOEMRecyclerView;
@Nullable
private AdapterOEMV1 mOEMAdapter;
@Nullable
private Adapter<?> mAdapter;
@NonNull
private final List<OnScrollListener> mScrollListeners = new ArrayList<>();
@Nullable
private ProxyRecyclerView mRecyclerView;
@Nullable
private CarUiLayoutStyle mLayoutStyle;
private int mHeight;
private int mWidth;
@NonNull
private final List<OnChildAttachStateChangeListener> mChildAttachStateChangeListeners =
new ArrayList<>();
public RecyclerViewAdapterV1(@NonNull Context context) {
this(context, null);
}
public RecyclerViewAdapterV1(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RecyclerViewAdapterV1(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle, 0);
// Background and padding to be handled by plugin implementation
setBackground(null);
setPadding(0, 0, 0, 0);
}
/**
* Called to pass the oem recyclerview implementation.
*
* @param oemRecyclerView plugin implementation of {@link CarUiRecyclerView}
*/
public void setRecyclerViewOEMV1(@NonNull RecyclerViewOEMV1 oemRecyclerView,
@Nullable RecyclerViewAttributesOEMV1 oemAttrs) {
mOEMRecyclerView = oemRecyclerView;
LayoutStyleOEMV1 oemLayoutStyle = mOEMRecyclerView.getLayoutStyle();
if (oemLayoutStyle.getLayoutType() == LayoutStyleOEMV1.LAYOUT_TYPE_GRID) {
CarUiGridLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
layoutStyle.setReverseLayout(oemLayoutStyle.getReverseLayout());
layoutStyle.setSpanCount(oemLayoutStyle.getSpanCount());
layoutStyle.setOrientation(oemLayoutStyle.getOrientation());
layoutStyle.setSize(oemAttrs == null
? RecyclerViewAttributesOEMV1.SIZE_LARGE : oemAttrs.getSize());
mLayoutStyle = layoutStyle;
} else {
CarUiLinearLayoutStyle layoutStyle = new CarUiLinearLayoutStyle();
layoutStyle.setReverseLayout(oemLayoutStyle.getReverseLayout());
layoutStyle.setOrientation(oemLayoutStyle.getOrientation());
layoutStyle.setSize(oemAttrs == null
? RecyclerViewAttributesOEMV1.SIZE_LARGE : oemAttrs.getSize());
mLayoutStyle = layoutStyle;
}
// Adding this parent so androidx PreferenceFragmentCompat doesn't add the ProxyRecyclerView
// to the view hierarchy
ViewGroup parent = new FrameLayout(getContext());
mRecyclerView = new ProxyRecyclerView(getContext(), this);
parent.addView(mRecyclerView);
mOEMRecyclerView.addOnScrollListener(this);
mOEMRecyclerView.addOnChildAttachStateChangeListener(this);
ViewGroup.LayoutParams params = new MarginLayoutParams(mWidth, mHeight);
addView(mOEMRecyclerView.getView(), params);
}
@Override
public void setLayoutManager(@Nullable LayoutManager layoutManager) {
if (layoutManager instanceof GridLayoutManager) {
setLayoutStyle(CarUiGridLayoutStyle.from(layoutManager));
} else {
setLayoutStyle(CarUiLinearLayoutStyle.from(layoutManager));
}
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
// Margin should be set by plugin implementation only
Rect marginRect = null;
if (params instanceof MarginLayoutParams) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) params;
marginRect = new Rect(
Math.max(marginLayoutParams.leftMargin, marginLayoutParams.getMarginStart()),
marginLayoutParams.topMargin, marginLayoutParams.rightMargin,
Math.max(marginLayoutParams.bottomMargin, marginLayoutParams.getMarginEnd()));
marginLayoutParams.setMargins(0, 0, 0, 0);
marginLayoutParams.setMarginStart(0);
marginLayoutParams.setMarginEnd(0);
}
if (mOEMRecyclerView != null) {
ViewGroup.LayoutParams pluginLayoutParams =
mOEMRecyclerView.getView().getLayoutParams();
// Account for 0dp in ConstraintLayout by swapping to MATCH_PARENT. If intentionally
// set to size of 0dp, this FrameLayout will have a 0dp size so MATCH_PARENT will
// still result in correct size
pluginLayoutParams.width =
params.width == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : params.width;
pluginLayoutParams.height =
params.height == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : params.height;
// Apply requested margins to plugin implementation layout
if (marginRect != null && pluginLayoutParams instanceof MarginLayoutParams) {
MarginLayoutParams rvMarginParams = (MarginLayoutParams) pluginLayoutParams;
rvMarginParams.setMargins(marginRect.left, marginRect.top, marginRect.right,
marginRect.bottom);
}
}
mWidth = params.width;
mHeight = params.height;
super.setLayoutParams(params);
}
@Override
public void setAlpha(float alpha) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.setAlpha(alpha);
}
}
@Override
public void setBackground(Drawable background) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.getView().setBackground(background);
}
}
@Override
public void setBackgroundColor(int color) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.getView().setBackgroundColor(color);
}
}
@Override
public void setBackgroundResource(int resid) {
if (mOEMRecyclerView != null) {
Drawable background = getResources().getDrawable(resid);
mOEMRecyclerView.getView().setBackground(background);
}
}
@Nullable
@Override
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
@Override
public int getRecyclerViewChildCount() {
return mOEMRecyclerView.getRecyclerViewChildCount();
}
@Override
public View getRecyclerViewChildAt(int index) {
return mOEMRecyclerView.getRecyclerViewChildAt(index);
}
private static int toInternalScrollState(int state) {
/* default to RecyclerViewOEMV1.SCROLL_STATE_IDLE */
int internalState = SCROLL_STATE_IDLE;
switch (state) {
case RecyclerViewOEMV1.SCROLL_STATE_DRAGGING:
internalState = SCROLL_STATE_DRAGGING;
break;
case RecyclerViewOEMV1.SCROLL_STATE_SETTLING:
internalState = SCROLL_STATE_SETTLING;
break;
}
return internalState;
}
@Override
public int getScrollState() {
return toInternalScrollState(mOEMRecyclerView.getScrollState());
}
@Override
public int getEndAfterPadding() {
return mOEMRecyclerView.getEndAfterPadding();
}
@Override
public int getStartAfterPadding() {
return mOEMRecyclerView.getStartAfterPadding();
}
@Override
public int getTotalSpace() {
return mOEMRecyclerView.getTotalSpace();
}
@NonNull
@Override
public View getView() {
return this;
}
@Override
public void invalidateItemDecorations() {
}
@Override
public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
}
@Override
public void removeItemDecorationAt(int index) {
}
@Nullable
@Override
public RecyclerView.ItemDecoration getItemDecorationAt(int index) {
return null;
}
@Override
public int getItemDecorationCount() {
return 0;
}
@Override
public void setContentDescription(CharSequence contentDescription) {
super.setContentDescription(contentDescription);
mOEMRecyclerView.setContentDescription(contentDescription);
}
@Override
public void setAdapter(RecyclerView.Adapter<?> adapter) {
if (adapter == null) {
mAdapter = null;
mOEMAdapter = null;
mOEMRecyclerView.setAdapter(null);
} else {
if (mAdapter instanceof OnAttachListener) {
((OnAttachListener) mAdapter).onDetachedFromCarUiRecyclerView(this);
}
mAdapter = adapter;
mOEMAdapter = new RecyclerViewAdapterAdapterV1(getContext(), adapter);
mOEMRecyclerView.setAdapter(mOEMAdapter);
if (adapter instanceof OnAttachListener) {
((OnAttachListener) adapter).onAttachedToCarUiRecyclerView(this);
}
}
}
@Override
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
}
@Override
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor, int index) {
}
@Override
public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
mChildAttachStateChangeListeners.add(listener);
}
@Override
public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
mChildAttachStateChangeListeners.remove(listener);
}
@Override
public void clearOnChildAttachStateChangeListeners() {
mChildAttachStateChangeListeners.clear();
mOEMRecyclerView.clearOnScrollListeners();
}
@Override
public void addOnScrollListener(@NonNull OnScrollListener listener) {
mScrollListeners.add(listener);
}
@Override
public void removeOnScrollListener(@NonNull OnScrollListener listener) {
mScrollListeners.remove(listener);
}
@Override
public void clearOnScrollListeners() {
mScrollListeners.clear();
mOEMRecyclerView.clearOnScrollListeners();
}
@Override
public void onScrollStateChanged(@NonNull RecyclerViewOEMV1 recyclerView, int newState) {
for (OnScrollListener listener : mScrollListeners) {
listener.onScrollStateChanged(this, toInternalScrollState(newState));
}
}
@Override
public void onScrolled(@NonNull RecyclerViewOEMV1 recyclerView, int dx, int dy) {
for (OnScrollListener listener : mScrollListeners) {
listener.onScrolled(this, dx, dy);
}
}
@Override
public void scrollToPosition(int position) {
mOEMRecyclerView.scrollToPosition(position);
}
@Override
public void smoothScrollBy(int dx, int dy) {
mOEMRecyclerView.smoothScrollBy(dx, dy);
}
@Override
public void smoothScrollToPosition(int position) {
mOEMRecyclerView.smoothScrollToPosition(position);
}
@Override
public ViewHolder findViewHolderForAdapterPosition(int position) {
ViewHolderOEMV1 viewHolder = mOEMRecyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder instanceof ViewHolderAdapterV1) {
return ((ViewHolderAdapterV1) viewHolder).getViewHolder();
}
return null;
}
@Override
public ViewHolder findViewHolderForLayoutPosition(int position) {
ViewHolderOEMV1 viewHolder = mOEMRecyclerView.findViewHolderForLayoutPosition(position);
if (viewHolder instanceof ViewHolderAdapterV1) {
return ((ViewHolderAdapterV1) viewHolder).getViewHolder();
}
return null;
}
@Override
public Adapter<?> getAdapter() {
return mAdapter;
}
@Override
public int getChildLayoutPosition(View child) {
return mOEMRecyclerView.getChildLayoutPosition(child);
}
@Override
public void setHasFixedSize(boolean hasFixedSize) {
mOEMRecyclerView.setHasFixedSize(hasFixedSize);
}
@Override
public void setOnFlingListener(OnFlingListener listener) {
// TODO
}
@Override
public boolean hasFixedSize() {
return mOEMRecyclerView.hasFixedSize();
}
/**
* @return null
* @deprecated LayoutManager will be implemented by OEMs, use other available APIs to get the
* required data
*/
@Nullable
@Override
@Deprecated
public LayoutManager getLayoutManager() {
return null;
}
@Override
public CarUiLayoutStyle getLayoutStyle() {
return mLayoutStyle;
}
@Override
public boolean canScrollHorizontally(int direction) {
return mOEMRecyclerView.getView().canScrollHorizontally(direction);
}
@Override
public boolean canScrollVertically(int direction) {
return mOEMRecyclerView.getView().canScrollVertically(direction);
}
@Override
public void setLayoutStyle(CarUiLayoutStyle layoutStyle) {
mLayoutStyle = layoutStyle;
if (layoutStyle == null) {
mOEMRecyclerView.setLayoutStyle(null);
return;
}
final LayoutStyleOEMV1 oemLayoutStyle = new LayoutStyleOEMV1() {
@Override
public int getSpanCount() {
return layoutStyle.getSpanCount();
}
@Override
public int getLayoutType() {
return layoutStyle.getLayoutType();
}
@Override
public int getOrientation() {
return layoutStyle.getOrientation();
}
@Override
public boolean getReverseLayout() {
return layoutStyle.getReverseLayout();
}
@Override
public int getSpanSize(int position) {
if (layoutStyle instanceof CarUiGridLayoutStyle) {
return ((CarUiGridLayoutStyle) layoutStyle).getSpanSizeLookup()
.getSpanSize(position);
}
return 1;
}
};
if (mOEMRecyclerView != null) {
mOEMRecyclerView.setLayoutStyle(oemLayoutStyle);
}
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.getView().setPadding(left, top, right, bottom);
}
}
@Override
public int getPaddingLeft() {
if (mOEMRecyclerView != null) {
return mOEMRecyclerView.getView().getPaddingLeft();
}
return super.getPaddingLeft();
}
@Override
public int getPaddingTop() {
if (mOEMRecyclerView != null) {
return mOEMRecyclerView.getView().getPaddingTop();
}
return super.getPaddingTop();
}
@Override
public int getPaddingRight() {
if (mOEMRecyclerView != null) {
return mOEMRecyclerView.getView().getPaddingRight();
}
return super.getPaddingRight();
}
@Override
public int getPaddingBottom() {
if (mOEMRecyclerView != null) {
return mOEMRecyclerView.getView().getPaddingBottom();
}
return super.getPaddingBottom();
}
@Override
public int getPaddingStart() {
boolean isLtr = getContext().getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
if (isLtr) {
return getPaddingLeft();
} else {
return getPaddingRight();
}
}
@Override
public int getPaddingEnd() {
boolean isLtr = getContext().getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
if (isLtr) {
return getPaddingRight();
} else {
return getPaddingLeft();
}
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.getView().setPaddingRelative(start, top, end, bottom);
}
}
@Override
public void setClipToPadding(boolean clipToPadding) {
if (mOEMRecyclerView != null) {
mOEMRecyclerView.setClipToPadding(clipToPadding);
}
}
@Override
public void setItemAnimator(ItemAnimator itemAnimator) {
}
@Override
public int findFirstCompletelyVisibleItemPosition() {
return mOEMRecyclerView != null ? mOEMRecyclerView
.findFirstCompletelyVisibleItemPosition() : 0;
}
@Override
public int findFirstVisibleItemPosition() {
return mOEMRecyclerView != null ? mOEMRecyclerView
.findFirstVisibleItemPosition() : 0;
}
@Override
public int findLastCompletelyVisibleItemPosition() {
return mOEMRecyclerView != null ? mOEMRecyclerView
.findLastCompletelyVisibleItemPosition() : 0;
}
@Override
public int findLastVisibleItemPosition() {
return mOEMRecyclerView != null ? mOEMRecyclerView
.findLastVisibleItemPosition() : 0;
}
@Override
public void setSpanSizeLookup(@NonNull SpanSizeLookup spanSizeLookup) {
if (mLayoutStyle instanceof CarUiGridLayoutStyle) {
((CarUiGridLayoutStyle) mLayoutStyle).setSpanSizeLookup(spanSizeLookup);
setLayoutStyle(mLayoutStyle);
}
}
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
for (OnChildAttachStateChangeListener listener : mChildAttachStateChangeListeners) {
listener.onChildViewAttachedToWindow(view);
}
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {
for (OnChildAttachStateChangeListener listener : mChildAttachStateChangeListeners) {
listener.onChildViewDetachedFromWindow(view);
}
}
}