Add a wrapper ContentLimiting adapter.
Also elevate ScrollingLimitedViewHolder to a more useful standalone public class.
BUG: 160737822
BUG: 159766205
Test: manual with paintbooth app
Change-Id: Ic7cc133a1a8fcb01dedcef22d75d79342960db48
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
index 9014054..615ac25 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
@@ -17,18 +17,12 @@
package com.android.car.ui.recyclerview;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.car.ui.R;
-import com.android.car.ui.utils.CarUiUtils;
-
/**
* A {@link RecyclerView.Adapter} that can limit its content based on a given length limit which
* can change at run-time.
@@ -50,7 +44,7 @@
* Override this method to provide your own alternative value if {@link Integer#MAX_VALUE} is
* a viewType value already in-use by your adapter.
*/
- protected int getScrollingLimitedMessageViewType() {
+ public int getScrollingLimitedMessageViewType() {
return SCROLLING_LIMITED_MESSAGE_VIEW_TYPE;
}
@@ -59,9 +53,7 @@
public final RecyclerView.ViewHolder onCreateViewHolder(
@NonNull ViewGroup parent, int viewType) {
if (viewType == getScrollingLimitedMessageViewType()) {
- View rootView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.car_ui_list_limiting_message, parent, false);
- return new ScrollingLimitedViewHolder(rootView);
+ return ScrollingLimitedViewHolder.create(parent);
}
return onCreateViewHolderImpl(parent, viewType);
@@ -80,12 +72,7 @@
@SuppressWarnings("unchecked")
public final void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (isContentLimited() && position == getScrollingLimitedMessagePosition()) {
- String message = holder.itemView.getContext()
- .getString(R.string.car_ui_scrolling_limited_message);
- if (mScrollingLimitedMessageResId != null) {
- message = holder.itemView.getContext().getString(mScrollingLimitedMessageResId);
- }
- ((ScrollingLimitedViewHolder) holder).setMessage(message);
+ ((ScrollingLimitedViewHolder) holder).bind(mScrollingLimitedMessageResId);
return;
}
onBindViewHolderImpl((T) holder, position);
@@ -259,24 +246,4 @@
notifyItemChanged(getScrollingLimitedMessagePosition());
}
}
-
- /**
- * {@link RecyclerView.ViewHolder} for the last item in a scrolling limited list.
- */
- public static class ScrollingLimitedViewHolder extends RecyclerView.ViewHolder {
-
- private final TextView mMessage;
-
- ScrollingLimitedViewHolder(@NonNull View itemView) {
- super(itemView);
- mMessage = CarUiUtils.findViewByRefId(itemView, R.id.car_ui_list_limiting_message);
- }
-
- /**
- * Sets the display message.
- */
- public void setMessage(String message) {
- mMessage.setText(message);
- }
- }
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
new file mode 100644
index 0000000..9a21641
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2020 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.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * A delegating implementation of {@link ContentLimiting} interface.
+ *
+ * <p>This class will provide content limiting capability to any {@link RecyclerView.Adapter} that
+ * is wrapped in it.
+ *
+ * @param <T> type of the {@link RecyclerView.ViewHolder} objects used by the delegate.
+ */
+public final class DelegatingContentLimitingAdapter<T extends RecyclerView.ViewHolder>
+ extends ContentLimitingAdapter<T> {
+ private static final int SCROLLING_LIMITED_MESSAGE_VIEW_TYPE = Integer.MAX_VALUE;
+ private static final int SCROLLING_LIMITED_MESSAGE_DEFAULT_POSITION_OFFSET = -1;
+
+ private final RecyclerView.Adapter<T> mDelegate;
+ private final int mScrollingLimitedMessageViewType;
+ private final int mScrollingLimitedMessagePositionOffset;
+ @IdRes
+ private final int mConfigId;
+
+ /**
+ * Constructs a {@link DelegatingContentLimitingAdapter} that uses {@link Integer#MAX_VALUE}
+ * for the scrolling limited message viewType and positions it at the very bottom of the list
+ * being content limited.
+ *
+ * <p>Use {@link #DelegatingContentLimitingAdapter(RecyclerView.Adapter, int, int, int)} if you
+ * need to customize any of the two default values above.
+ *
+ * @param delegate - the {@link RecyclerView.Adapter} whose content needs to be limited.
+ * @param configId - an Id Resource that can be used to identify said adapter.
+ */
+ public DelegatingContentLimitingAdapter(
+ RecyclerView.Adapter<T> delegate,
+ @IdRes int configId) {
+ this(delegate,
+ configId,
+ SCROLLING_LIMITED_MESSAGE_VIEW_TYPE,
+ SCROLLING_LIMITED_MESSAGE_DEFAULT_POSITION_OFFSET);
+ }
+
+ /**
+ * Constructs a {@link DelegatingContentLimitingAdapter}.
+ *
+ * @param delegate - the {@link RecyclerView.Adapter} whose content needs to be limited.
+ * @param configId - an Id Resource that can be used to identify said adapter.
+ * @param viewType - viewType value for the scrolling limited message
+ * @param offset - offset of the position of the scrolling limited message. Negative values
+ * will be treated as a "bottom offset", i.e. they represent the value to
+ * subtract from {@link #getItemCount()} to get to the actual position of the
+ * message. For example, by default the offset is -1, meaning the position of
+ * the scrolling limited message will be getItemCount() - 1, which in a list
+ * indexed at 0 means the very last item. Positive values will be treated as
+ * "top offset", so an offset of 0 will put the scrolling limited message at the
+ * very top of the list.
+ */
+ public DelegatingContentLimitingAdapter(RecyclerView.Adapter<T> delegate,
+ @IdRes int configId,
+ int viewType,
+ int offset) {
+ mDelegate = delegate;
+ mConfigId = configId;
+ mScrollingLimitedMessageViewType = viewType;
+ mScrollingLimitedMessagePositionOffset = offset;
+ mDelegate.registerAdapterDataObserver(new Observer());
+ }
+
+ private class Observer extends RecyclerView.AdapterDataObserver {
+
+ @Override
+ public void onChanged() {
+ DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ DelegatingContentLimitingAdapter.this
+ .notifyItemRangeChanged(positionStart, itemCount);
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
+ DelegatingContentLimitingAdapter.this
+ .notifyItemRangeChanged(positionStart, itemCount, payload);
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ DelegatingContentLimitingAdapter.this
+ .notifyItemRangeInserted(positionStart, itemCount);
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ DelegatingContentLimitingAdapter.this
+ .notifyItemRangeRemoved(positionStart, itemCount);
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ @NonNull
+ public T onCreateViewHolderImpl(@NonNull ViewGroup parent, int viewType) {
+ return mDelegate.onCreateViewHolder(parent, viewType);
+ }
+
+ @Override
+ public void onBindViewHolderImpl(T holder, int position) {
+ mDelegate.onBindViewHolder(holder, position);
+ }
+
+ @Override
+ public int getItemViewTypeImpl(int position) {
+ return mDelegate.getItemViewType(position);
+ }
+
+ @Override
+ protected void onViewRecycledImpl(@NonNull T holder) {
+ mDelegate.onViewRecycled(holder);
+ }
+
+ @Override
+ protected boolean onFailedToRecycleViewImpl(@NonNull T holder) {
+ return mDelegate.onFailedToRecycleView(holder);
+ }
+
+ @Override
+ protected void onViewAttachedToWindowImpl(@NonNull T holder) {
+ mDelegate.onViewAttachedToWindow(holder);
+ }
+
+ @Override
+ protected void onViewDetachedFromWindowImpl(@NonNull T holder) {
+ mDelegate.onViewDetachedFromWindow(holder);
+ }
+
+ @Override
+ public void setHasStableIds(boolean hasStableIds) {
+ mDelegate.setHasStableIds(hasStableIds);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mDelegate.getItemId(position);
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+ mDelegate.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+ mDelegate.onDetachedFromRecyclerView(recyclerView);
+ }
+
+ @Override
+ public int getUnrestrictedItemCount() {
+ return mDelegate.getItemCount();
+ }
+
+ @Override
+ @IdRes
+ public int getConfigurationId() {
+ return mConfigId;
+ }
+
+ @Override
+ public int getScrollingLimitedMessageViewType() {
+ return mScrollingLimitedMessageViewType;
+ }
+
+ @Override
+ protected int getScrollingLimitedMessagePosition() {
+ if (mScrollingLimitedMessagePositionOffset < 0) {
+ // For negative values, treat them as a bottom offset.
+ return getItemCount() + mScrollingLimitedMessagePositionOffset;
+ } else {
+ // For positive values, treat them like a top offset.
+ return mScrollingLimitedMessagePositionOffset;
+ }
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java
new file mode 100644
index 0000000..63b8692
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
+
+/**
+ * {@link RecyclerView.ViewHolder} for the last item in a scrolling limited list.
+ */
+public final class ScrollingLimitedViewHolder extends RecyclerView.ViewHolder {
+
+ private final TextView mMessage;
+
+ /**
+ * Return an instance of {@link ScrollingLimitedViewHolder} with an already inflated root view.
+ * @param parent - the parent {@link ViewGroup} to use during inflation of the root view.
+ */
+ public static ScrollingLimitedViewHolder create(@NonNull ViewGroup parent) {
+ View rootView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.car_ui_list_limiting_message, parent, false);
+ return new ScrollingLimitedViewHolder(rootView);
+ }
+
+ ScrollingLimitedViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mMessage = CarUiUtils.requireViewByRefId(itemView, R.id.car_ui_list_limiting_message);
+ }
+
+ /**
+ * Update the content of this {@link ScrollingLimitedViewHolder} object using the provided
+ * message String resource id.
+ * @param messageId
+ */
+ public void bind(@StringRes @Nullable Integer messageId) {
+ int resId = (messageId != null) ? messageId : R.string.car_ui_scrolling_limited_message;
+ mMessage.setText(mMessage.getContext().getString(resId));
+ }
+}