Implement header list item.
Bug: 143234489
Test: CarUiListItemTest
Change-Id: I1304ff34535666afd3fcc82653c8e196cb7586f4
diff --git a/car-ui-lib/res/layout/car_ui_header_list_item.xml b/car-ui-lib/res/layout/car_ui_header_list_item.xml
new file mode 100644
index 0000000..c1fa1dc
--- /dev/null
+++ b/car-ui-lib/res/layout/car_ui_header_list_item.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_ui_header_list_item_height">
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/car_ui_list_item_start_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/car_ui_list_item_header_start_inset" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.CarUi.ListItem"
+ app:layout_constraintBottom_toTopOf="@+id/body"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/car_ui_list_item_start_guideline"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="packed" />
+
+ <TextView
+ android:id="@+id/body"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/car_ui_list_item_start_guideline"
+ app:layout_constraintTop_toBottomOf="@+id/title" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/car-ui-lib/res/values/dimens.xml b/car-ui-lib/res/values/dimens.xml
index e025301..1ffa471 100644
--- a/car-ui-lib/res/values/dimens.xml
+++ b/car-ui-lib/res/values/dimens.xml
@@ -176,6 +176,8 @@
<!-- List item -->
<dimen name="car_ui_list_item_height">116dp</dimen>
+ <dimen name="car_ui_header_list_item_height">116dp</dimen>
+ <dimen name="car_ui_list_item_header_start_inset">0dp</dimen>
<dimen name="car_ui_list_item_start_inset">0dp</dimen>
<dimen name="car_ui_list_item_end_inset">0dp</dimen>
<dimen name="car_ui_list_item_text_start_margin">0dp</dimen>
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
new file mode 100644
index 0000000..b1ff760
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
@@ -0,0 +1,247 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Definition of list items that can be inserted into {@link CarUiListItemAdapter}.
+ */
+public class CarUiContentListItem extends CarUiListItem {
+
+ /**
+ * Callback to be invoked when the checked state of a list item changed.
+ */
+ public interface OnCheckedChangedListener {
+ /**
+ * Called when the checked state of a list item has changed.
+ *
+ * @param isChecked new checked state of list item.
+ */
+ void onCheckedChanged(boolean isChecked);
+ }
+
+ /**
+ * Enum of secondary action types of a list item.
+ */
+ public enum Action {
+ /**
+ * For an action value of NONE, no action element is shown for a list item.
+ */
+ NONE,
+ /**
+ * For an action value of SWITCH, a switch is shown for the action element of the list item.
+ */
+ SWITCH,
+ /**
+ * For an action value of CHECK_BOX, a checkbox is shown for the action element of the list
+ * item.
+ */
+ CHECK_BOX,
+ /**
+ * For an action value of ICON, an icon is shown for the action element of the list item.
+ */
+ ICON
+ }
+
+ private Drawable mIcon;
+ @Nullable
+ private Drawable mSupplementalIcon;
+ private CharSequence mTitle;
+ private CharSequence mBody;
+ private Action mAction;
+ private boolean mIsActionDividerVisible;
+ private boolean mIsChecked;
+ private OnCheckedChangedListener mOnCheckedChangedListener;
+ private View.OnClickListener mSupplementalIconOnClickListener;
+
+ public CarUiContentListItem() {
+ mAction = Action.NONE;
+ }
+
+ /**
+ * Returns the title of the item.
+ */
+ @Nullable
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the title of the item.
+ *
+ * @param title text to display as title.
+ */
+ public void setTitle(@NonNull CharSequence title) {
+ mTitle = title;
+ }
+
+ /**
+ * Returns the body text of the item.
+ */
+ @Nullable
+ public CharSequence getBody() {
+ return mBody;
+ }
+
+ /**
+ * Sets the body of the item.
+ *
+ * @param body text to display as body text.
+ */
+ public void setBody(@NonNull CharSequence body) {
+ mBody = body;
+ }
+
+ /**
+ * Returns the icon of the item.
+ */
+ @Nullable
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Sets the icon of the item.
+ *
+ * @param icon the icon to display.
+ */
+ public void setIcon(@Nullable Drawable icon) {
+ mIcon = icon;
+ }
+
+ /**
+ * Returns {@code true} if the item is checked. Will always return {@code false} when the action
+ * type for the item is {@code Action.NONE}.
+ */
+ public boolean isChecked() {
+ return mIsChecked;
+ }
+
+ /**
+ * Sets the checked state of the item.
+ *
+ * @param checked the checked state for the item.
+ */
+ public void setChecked(boolean checked) {
+ // Checked state can only be set when action type is checkbox or switch.
+ if (mAction == Action.CHECK_BOX || mAction == Action.SWITCH) {
+ mIsChecked = checked;
+ }
+ }
+
+ /**
+ * Sets the visibility of the action divider.
+ *
+ * @param visible visibility of the action divider.
+ */
+ public void setActionDividerVisible(boolean visible) {
+ mIsActionDividerVisible = visible;
+ }
+
+ /**
+ * Returns {@code true} if the action divider is visible.
+ */
+ public boolean isActionDividerVisible() {
+ return mIsActionDividerVisible;
+ }
+
+ /**
+ * Returns the action type for the item.
+ */
+ public Action getAction() {
+ return mAction;
+ }
+
+ /**
+ * Sets the action type for the item.
+ *
+ * @param action the action type for the item.
+ */
+ public void setAction(Action action) {
+ mAction = action;
+
+ // Cannot have checked state be true when there action type is not checkbox or switch.
+ if (mAction != Action.CHECK_BOX && mAction != Action.SWITCH) {
+ mIsChecked = false;
+ }
+ }
+
+ /**
+ * Returns the supplemental icon for the item.
+ */
+ @Nullable
+ public Drawable getSupplementalIcon() {
+ if (mAction != Action.ICON) {
+ return null;
+ }
+
+ return mSupplementalIcon;
+ }
+
+ /**
+ * Sets supplemental icon to be displayed in a list item.
+ *
+ * @param icon the Drawable to set as the icon, or null to clear the content.
+ */
+ public void setSupplementalIcon(@Nullable Drawable icon) {
+ setSupplementalIcon(icon, null);
+ }
+
+ /**
+ * Sets supplemental icon to be displayed in a list item.
+ *
+ * @param icon the Drawable to set as the icon, or null to clear the content.
+ * @param listener the callback that is invoked when the icon is clicked.
+ */
+ public void setSupplementalIcon(@Nullable Drawable icon,
+ @Nullable View.OnClickListener listener) {
+ mAction = Action.ICON;
+
+ // Cannot have checked state when action type is {@code Action.ICON}.
+ mIsChecked = false;
+
+ mSupplementalIcon = icon;
+ mSupplementalIconOnClickListener = listener;
+ }
+
+ View.OnClickListener getSupplementalIconOnClickListener() {
+ return mSupplementalIconOnClickListener;
+ }
+
+ /**
+ * Registers a callback to be invoked when the checked state of list item changes.
+ *
+ * <p>Checked state changes can take place when the action type is {@code Action.SWITCH} or
+ * {@code Action.CHECK_BOX}.
+ *
+ * @param listener callback to be invoked when the checked state shown in the UI changes.
+ */
+ public void setOnCheckedChangedListener(
+ @NonNull OnCheckedChangedListener listener) {
+ mOnCheckedChangedListener = listener;
+ }
+
+ @Nullable
+ OnCheckedChangedListener getOnCheckedChangedListener() {
+ return mOnCheckedChangedListener;
+ }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiHeaderListItem.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiHeaderListItem.java
new file mode 100644
index 0000000..57fd755
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiHeaderListItem.java
@@ -0,0 +1,51 @@
+/*
+ * 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 androidx.annotation.NonNull;
+
+/**
+ * Definition of list item header that can be inserted into {@link CarUiListItemAdapter}.
+ */
+public class CarUiHeaderListItem extends CarUiListItem {
+
+ private CharSequence mTitle;
+ private CharSequence mBody;
+
+ public CarUiHeaderListItem(@NonNull CharSequence title) {
+ this(title, "");
+ }
+
+ public CarUiHeaderListItem(@NonNull CharSequence title, @NonNull CharSequence body) {
+ mTitle = title;
+ mBody = body;
+ }
+
+ /**
+ * Returns the title text for the header.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the body text for the header.
+ */
+ public CharSequence getBody() {
+ return mBody;
+ }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java
index 88cd938..c894774 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java
@@ -1,247 +1,7 @@
-/*
- * 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.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
- * Definition of items that can be inserted into {@link CarUiListItemAdapter}.
+ * All items that can be inserted into {@link CarUiListItemAdapter} must extend this class.
*/
-public class CarUiListItem {
-
- /**
- * Callback to be invoked when the checked state of a list item changed.
- */
- public interface OnCheckedChangedListener {
- /**
- * Called when the checked state of a list item has changed.
- *
- * @param isChecked new checked state of list item.
- */
- void onCheckedChanged(boolean isChecked);
- }
-
- /**
- * Enum of secondary action types of a list item.
- */
- public enum Action {
- /**
- * For an action value of NONE, no action element is shown for a list item.
- */
- NONE,
- /**
- * For an action value of SWITCH, a switch is shown for the action element of the list item.
- */
- SWITCH,
- /**
- * For an action value of CHECK_BOX, a checkbox is shown for the action element of the list
- * item.
- */
- CHECK_BOX,
- /**
- * For an action value of ICON, an icon is shown for the action element of the list item.
- */
- ICON
- }
-
- private Drawable mIcon;
- @Nullable
- private Drawable mSupplementalIcon;
- private CharSequence mTitle;
- private CharSequence mBody;
- private Action mAction;
- private boolean mIsActionDividerVisible;
- private boolean mIsChecked;
- private OnCheckedChangedListener mOnCheckedChangedListener;
- private View.OnClickListener mSupplementalIconOnClickListener;
-
- public CarUiListItem() {
- mAction = Action.NONE;
- }
-
- /**
- * Returns the title of the item.
- */
- @Nullable
- public CharSequence getTitle() {
- return mTitle;
- }
-
- /**
- * Sets the title of the item.
- *
- * @param title text to display as title.
- */
- public void setTitle(@NonNull CharSequence title) {
- mTitle = title;
- }
-
- /**
- * Returns the body text of the item.
- */
- @Nullable
- public CharSequence getBody() {
- return mBody;
- }
-
- /**
- * Sets the body of the item.
- *
- * @param body text to display as body text.
- */
- public void setBody(@NonNull CharSequence body) {
- mBody = body;
- }
-
- /**
- * Returns the icon of the item.
- */
- @Nullable
- public Drawable getIcon() {
- return mIcon;
- }
-
- /**
- * Sets the icon of the item.
- *
- * @param icon the icon to display.
- */
- public void setIcon(@Nullable Drawable icon) {
- mIcon = icon;
- }
-
- /**
- * Returns {@code true} if the item is checked. Will always return {@code false} when the action
- * type for the item is {@code Action.NONE}.
- */
- public boolean isChecked() {
- return mIsChecked;
- }
-
- /**
- * Sets the checked state of the item.
- *
- * @param checked the checked state for the item.
- */
- public void setChecked(boolean checked) {
- // Checked state can only be set when action type is checkbox or switch.
- if (mAction == Action.CHECK_BOX || mAction == Action.SWITCH) {
- mIsChecked = checked;
- }
- }
-
- /**
- * Sets the visibility of the action divider.
- *
- * @param visible visibility of the action divider.
- */
- public void setActionDividerVisible(boolean visible) {
- mIsActionDividerVisible = visible;
- }
-
- /**
- * Returns {@code true} if the action divider is visible.
- */
- public boolean isActionDividerVisible() {
- return mIsActionDividerVisible;
- }
-
- /**
- * Returns the action type for the item.
- */
- public Action getAction() {
- return mAction;
- }
-
- /**
- * Sets the action type for the item.
- *
- * @param action the action type for the item.
- */
- public void setAction(Action action) {
- mAction = action;
-
- // Cannot have checked state be true when there action type is not checkbox or switch.
- if (mAction != Action.CHECK_BOX && mAction != Action.SWITCH) {
- mIsChecked = false;
- }
- }
-
- /**
- * Returns the supplemental icon for the item.
- */
- @Nullable
- public Drawable getSupplementalIcon() {
- if (mAction != Action.ICON) {
- return null;
- }
-
- return mSupplementalIcon;
- }
-
- /**
- * Sets supplemental icon to be displayed in a list item.
- *
- * @param icon the Drawable to set as the icon, or null to clear the content.
- */
- public void setSupplementalIcon(@Nullable Drawable icon) {
- setSupplementalIcon(icon, null);
- }
-
- /**
- * Sets supplemental icon to be displayed in a list item.
- *
- * @param icon the Drawable to set as the icon, or null to clear the content.
- * @param listener the callback that is invoked when the icon is clicked.
- */
- public void setSupplementalIcon(@Nullable Drawable icon,
- @Nullable View.OnClickListener listener) {
- mAction = Action.ICON;
-
- // Cannot have checked state when action type is {@code Action.ICON}.
- mIsChecked = false;
-
- mSupplementalIcon = icon;
- mSupplementalIconOnClickListener = listener;
- }
-
- View.OnClickListener getSupplementalIconOnClickListener() {
- return mSupplementalIconOnClickListener;
- }
-
- /**
- * Registers a callback to be invoked when the checked state of list item changes.
- *
- * <p>Checked state changes can take place when the action type is {@code Action.SWITCH} or
- * {@code Action.CHECK_BOX}.
- *
- * @param listener callback to be invoked when the checked state shown in the UI changes.
- */
- public void setOnCheckedChangedListener(
- @NonNull OnCheckedChangedListener listener) {
- mOnCheckedChangedListener = listener;
- }
-
- @Nullable
- OnCheckedChangedListener getOnCheckedChangedListener() {
- return mOnCheckedChangedListener;
- }
+public abstract class CarUiListItem {
}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index 5a594e3..af705c9 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -34,16 +34,20 @@
import java.util.List;
/**
- * Adapter for {@link CarUiRecyclerView} to display {@link CarUiListItem}.
+ * Adapter for {@link CarUiRecyclerView} to display {@link CarUiContentListItem} and {@link
+ * CarUiHeaderListItem}.
*
* <ul>
* <li> Implements {@link CarUiRecyclerView.ItemCap} - defaults to unlimited item count.
* </ul>
*/
public class CarUiListItemAdapter extends
- RecyclerView.Adapter<CarUiListItemAdapter.ViewHolder> implements
+ RecyclerView.Adapter<RecyclerView.ViewHolder> implements
CarUiRecyclerView.ItemCap {
+ private static final int VIEW_TYPE_LIST_ITEM = 1;
+ private static final int VIEW_TYPE_LIST_HEADER = 2;
+
private List<CarUiListItem> mItems;
private int mMaxItems = CarUiRecyclerView.ItemCap.UNLIMITED;
@@ -53,11 +57,20 @@
@NonNull
@Override
- public ViewHolder onCreateViewHolder(
+ public RecyclerView.ViewHolder onCreateViewHolder(
@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- View view = inflater.inflate(R.layout.car_ui_list_item, parent, false);
- return new ViewHolder(view);
+
+ switch (viewType) {
+ case VIEW_TYPE_LIST_ITEM:
+ return new ListItemViewHolder(
+ inflater.inflate(R.layout.car_ui_list_item, parent, false));
+ case VIEW_TYPE_LIST_HEADER:
+ return new HeaderViewHolder(
+ inflater.inflate(R.layout.car_ui_header_list_item, parent, false));
+ default:
+ throw new IllegalStateException("Unknown item type.");
+ }
}
/**
@@ -72,8 +85,54 @@
}
@Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- CarUiListItem item = mItems.get(position);
+ public int getItemViewType(int position) {
+ if (mItems.get(position) instanceof CarUiContentListItem) {
+ return VIEW_TYPE_LIST_ITEM;
+ } else if (mItems.get(position) instanceof CarUiHeaderListItem) {
+ return VIEW_TYPE_LIST_HEADER;
+ }
+
+ throw new IllegalStateException("Unknown view type.");
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (holder.getItemViewType()) {
+ case VIEW_TYPE_LIST_ITEM:
+ if (!(holder instanceof ListItemViewHolder)) {
+ throw new IllegalStateException("Incorrect view holder type for list item.");
+ }
+
+ CarUiListItem item = mItems.get(position);
+ if (!(item instanceof CarUiContentListItem)) {
+ throw new IllegalStateException(
+ "Expected item to be bound to viewholder to be instance of "
+ + "CarUiContentListItem.");
+ }
+
+ onBindListItemViewHolder((ListItemViewHolder) holder, (CarUiContentListItem) item);
+ break;
+ case VIEW_TYPE_LIST_HEADER:
+ if (!(holder instanceof HeaderViewHolder)) {
+ throw new IllegalStateException("Incorrect view holder type for list item.");
+ }
+
+ CarUiListItem header = mItems.get(position);
+ if (!(header instanceof CarUiHeaderListItem)) {
+ throw new IllegalStateException(
+ "Expected item to be bound to viewholder to be instance of "
+ + "CarUiHeaderListItem.");
+ }
+
+ onBindHeaderViewHolder((HeaderViewHolder) holder, (CarUiHeaderListItem) header);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item view type.");
+ }
+ }
+
+ private void onBindListItemViewHolder(@NonNull ListItemViewHolder holder,
+ @NonNull CarUiContentListItem item) {
CharSequence title = item.getTitle();
CharSequence body = item.getBody();
Drawable icon = item.getIcon();
@@ -116,7 +175,7 @@
switchWidget.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
item.setChecked(isChecked);
- CarUiListItem.OnCheckedChangedListener itemListener =
+ CarUiContentListItem.OnCheckedChangedListener itemListener =
item.getOnCheckedChangedListener();
if (itemListener != null) {
itemListener.onCheckedChanged(isChecked);
@@ -132,7 +191,7 @@
checkBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
item.setChecked(isChecked);
- CarUiListItem.OnCheckedChangedListener itemListener =
+ CarUiContentListItem.OnCheckedChangedListener itemListener =
item.getOnCheckedChangedListener();
if (itemListener != null) {
itemListener.onCheckedChanged(isChecked);
@@ -160,6 +219,18 @@
}
}
+ private void onBindHeaderViewHolder(@NonNull HeaderViewHolder holder,
+ @NonNull CarUiHeaderListItem item) {
+ holder.getTitle().setText(item.getTitle());
+
+ CharSequence body = item.getBody();
+ if (!TextUtils.isEmpty(body)) {
+ holder.getBody().setText(body);
+ } else {
+ holder.getBody().setVisibility(View.GONE);
+ }
+ }
+
@Override
public int getItemCount() {
return mMaxItems == CarUiRecyclerView.ItemCap.UNLIMITED
@@ -173,9 +244,9 @@
}
/**
- * Holds views of {@link CarUiListItem}.
+ * Holds views of {@link CarUiContentListItem}.
*/
- static class ViewHolder extends RecyclerView.ViewHolder {
+ static class ListItemViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
private TextView mBody;
@@ -187,7 +258,7 @@
private CheckBox mCheckBox;
private ImageView mSupplementalIcon;
- ViewHolder(@NonNull View itemView) {
+ ListItemViewHolder(@NonNull View itemView) {
super(itemView);
mTitle = itemView.requireViewById(R.id.title);
mBody = itemView.requireViewById(R.id.body);
@@ -246,4 +317,29 @@
}
}
+
+ /**
+ * Holds views of {@link CarUiHeaderListItem}.
+ */
+ static class HeaderViewHolder extends RecyclerView.ViewHolder {
+
+ private TextView mTitle;
+ private TextView mBody;
+
+ HeaderViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mTitle = itemView.requireViewById(R.id.title);
+ mBody = itemView.requireViewById(R.id.body);
+ }
+
+ @NonNull
+ TextView getTitle() {
+ return mTitle;
+ }
+
+ @NonNull
+ TextView getBody() {
+ return mBody;
+ }
+ }
}
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index d0dfd57..6d809ca 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -46,11 +46,13 @@
<public type="dimen" name="car_ui_dialog_edittext_margin_end"/>
<public type="dimen" name="car_ui_dialog_edittext_margin_start"/>
<public type="dimen" name="car_ui_dialog_edittext_margin_top"/>
+ <public type="dimen" name="car_ui_header_list_item_height"/>
<public type="dimen" name="car_ui_letter_spacing_body1"/>
<public type="dimen" name="car_ui_letter_spacing_body3"/>
<public type="dimen" name="car_ui_list_item_action_divider_height"/>
<public type="dimen" name="car_ui_list_item_action_divider_width"/>
<public type="dimen" name="car_ui_list_item_end_inset"/>
+ <public type="dimen" name="car_ui_list_item_header_start_inset"/>
<public type="dimen" name="car_ui_list_item_height"/>
<public type="dimen" name="car_ui_list_item_icon_container_width"/>
<public type="dimen" name="car_ui_list_item_icon_size"/>
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
index 0d65ee9..636b0ba 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
@@ -22,13 +22,17 @@
import android.widget.Toast;
import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiHeaderListItem;
import com.android.car.ui.recyclerview.CarUiListItem;
import com.android.car.ui.recyclerview.CarUiListItemAdapter;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
import java.util.ArrayList;
-/** Activity that shows {@link CarUiRecyclerView} with dummy {@link CarUiListItem} entries. */
+/**
+ * Activity that shows {@link CarUiRecyclerView} with dummy {@link CarUiContentListItem} entries
+ */
public class CarUiListItemActivity extends Activity {
private final ArrayList<CarUiListItem> mData = new ArrayList<>();
@@ -46,56 +50,61 @@
private ArrayList<CarUiListItem> generateDummyData() {
Context context = this;
- CarUiListItem item = new CarUiListItem();
+ CarUiHeaderListItem header = new CarUiHeaderListItem("First header");
+ mData.add(header);
+
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
item.setBody("Test body");
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setTitle("Test title with no body");
mData.add(item);
- item = new CarUiListItem();
+ header = new CarUiHeaderListItem("Random header", "with header body");
+ mData.add(header);
+
+ item = new CarUiContentListItem();
item.setBody("Test body with no title");
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setTitle("Test Title");
item.setIcon(getDrawable(R.drawable.ic_launcher));
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setTitle("Test Title");
item.setBody("Test body text");
item.setIcon(getDrawable(R.drawable.ic_launcher));
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setIcon(getDrawable(R.drawable.ic_launcher));
item.setTitle("Title -- Item with checkbox");
item.setBody("Will present toast on change of selection state.");
item.setOnCheckedChangedListener(
(isChecked) -> Toast.makeText(context,
"Item checked state is: " + isChecked, Toast.LENGTH_SHORT).show());
- item.setAction(CarUiListItem.Action.CHECK_BOX);
+ item.setAction(CarUiContentListItem.Action.CHECK_BOX);
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setIcon(getDrawable(R.drawable.ic_launcher));
- item.setBody("Body -- Item with switch -- and divider");
- item.setActionDividerVisible(true);
- item.setAction(CarUiListItem.Action.SWITCH);
+ item.setBody("Body -- Item with switch");
+ item.setAction(CarUiContentListItem.Action.SWITCH);
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setIcon(getDrawable(R.drawable.ic_launcher));
item.setTitle("Title -- Item with checkbox");
item.setBody("Item is initially checked");
- item.setAction(CarUiListItem.Action.CHECK_BOX);
+ item.setAction(CarUiContentListItem.Action.CHECK_BOX);
item.setChecked(true);
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setIcon(getDrawable(R.drawable.ic_launcher));
item.setTitle("Title");
item.setBody("Random body text -- with action divider");
@@ -104,14 +113,14 @@
item.setChecked(true);
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setIcon(getDrawable(R.drawable.ic_launcher));
item.setTitle("Null supplemental icon");
- item.setAction(CarUiListItem.Action.ICON);
+ item.setAction(CarUiContentListItem.Action.ICON);
item.setChecked(true);
mData.add(item);
- item = new CarUiListItem();
+ item = new CarUiContentListItem();
item.setTitle("Supplemental icon with listener");
item.setSupplementalIcon(getDrawable(R.drawable.ic_launcher),
v -> Toast.makeText(context, "Clicked supplemental icon",
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
index 972b7c5..b89daa2 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
@@ -49,7 +49,7 @@
private Context mContext;
@Mock
- CarUiListItem.OnCheckedChangedListener mOnCheckedChangedListener;
+ CarUiContentListItem.OnCheckedChangedListener mOnCheckedChangedListener;
@Before
public void setUp() {
@@ -58,8 +58,13 @@
mListView = new CarUiRecyclerView(mContext);
}
- private CarUiListItemAdapter.ViewHolder getViewHolderAtPosition(int position) {
- return (CarUiListItemAdapter.ViewHolder) mListView.findViewHolderForAdapterPosition(
+ private CarUiListItemAdapter.ListItemViewHolder getListItemViewHolderAtPosition(int position) {
+ return (CarUiListItemAdapter.ListItemViewHolder) mListView.findViewHolderForAdapterPosition(
+ position);
+ }
+
+ private CarUiListItemAdapter.HeaderViewHolder getHeaderViewHolderAtPosition(int position) {
+ return (CarUiListItemAdapter.HeaderViewHolder) mListView.findViewHolderForAdapterPosition(
position);
}
@@ -83,17 +88,21 @@
public void testItemVisibility_withTitle() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- assertThat(getViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIconContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getActionContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getIconContainer().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getActionContainer().getVisibility()).isNotEqualTo(
View.VISIBLE);
}
@@ -101,18 +110,22 @@
public void testItemVisibility_withTitle_withBody() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
item.setBody("Test body");
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- assertThat(getViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getBody().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIconContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getActionContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getBody().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getIconContainer().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getActionContainer().getVisibility()).isNotEqualTo(
View.VISIBLE);
}
@@ -120,19 +133,23 @@
public void testItemVisibility_withTitle_withIcon() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
item.setIcon(mContext.getDrawable(R.drawable.car_ui_icon_close));
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- assertThat(getViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIconContainer().getVisibility()).isEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIcon().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getActionContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getIconContainer().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getIcon().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getActionContainer().getVisibility()).isNotEqualTo(
View.VISIBLE);
}
@@ -140,47 +157,56 @@
public void testItemVisibility_withTitle_withCheckbox() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
- item.setAction(CarUiListItem.Action.CHECK_BOX);
+ item.setAction(CarUiContentListItem.Action.CHECK_BOX);
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- assertThat(getViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIconContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getActionContainer().getVisibility()).isEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getBody().getVisibility()).isNotEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getSwitch().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getIconContainer().getVisibility()).isNotEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getCheckBox().getVisibility()).isEqualTo(
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getActionContainer().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getCheckBox().isChecked()).isEqualTo(false);
+ assertThat(getListItemViewHolderAtPosition(0).getSwitch().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getCheckBox().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getCheckBox().isChecked()).isEqualTo(false);
}
@Test
public void testItemVisibility_withTitle_withBody_withSwitch() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
item.setBody("Body text");
- item.setAction(CarUiListItem.Action.SWITCH);
+ item.setAction(CarUiContentListItem.Action.SWITCH);
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- assertThat(getViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getBody().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getIconContainer().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getTitle().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getActionContainer().getVisibility()).isEqualTo(
+ assertThat(getListItemViewHolderAtPosition(0).getBody().getVisibility()).isEqualTo(
View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getSwitch().getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(getViewHolderAtPosition(0).getSwitch().isChecked()).isEqualTo(false);
- assertThat(getViewHolderAtPosition(0).getCheckBox().getVisibility()).isNotEqualTo(
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getIconContainer().getVisibility()).isNotEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(
+ 0).getActionContainer().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getSwitch().getVisibility()).isEqualTo(
+ View.VISIBLE);
+ assertThat(getListItemViewHolderAtPosition(0).getSwitch().isChecked()).isEqualTo(false);
+ assertThat(getListItemViewHolderAtPosition(0).getCheckBox().getVisibility()).isNotEqualTo(
View.VISIBLE);
}
@@ -188,16 +214,16 @@
public void testCheckedState_switch() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
- item.setChecked(true);
item.setOnCheckedChangedListener(mOnCheckedChangedListener);
- item.setAction(CarUiListItem.Action.SWITCH);
+ item.setAction(CarUiContentListItem.Action.SWITCH);
+ item.setChecked(true);
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- Switch switchWidget = getViewHolderAtPosition(0).getSwitch();
+ Switch switchWidget = getListItemViewHolderAtPosition(0).getSwitch();
assertThat(switchWidget.isChecked()).isEqualTo(true);
switchWidget.performClick();
@@ -209,19 +235,56 @@
public void testCheckedState_checkbox() {
List<CarUiListItem> items = new ArrayList<>();
- CarUiListItem item = new CarUiListItem();
+ CarUiContentListItem item = new CarUiContentListItem();
item.setTitle("Test title");
- item.setAction(CarUiListItem.Action.CHECK_BOX);
+ item.setAction(CarUiContentListItem.Action.CHECK_BOX);
item.setOnCheckedChangedListener(mOnCheckedChangedListener);
items.add(item);
updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
- CheckBox checkBox = getViewHolderAtPosition(0).getCheckBox();
+ CheckBox checkBox = getListItemViewHolderAtPosition(0).getCheckBox();
assertThat(checkBox.isChecked()).isEqualTo(false);
checkBox.performClick();
assertThat(checkBox.isChecked()).isEqualTo(true);
verify(mOnCheckedChangedListener, times(1)).onCheckedChanged(true);
}
+
+ @Test
+ public void testHeader_onlyTitle() {
+ List<CarUiListItem> items = new ArrayList<>();
+
+ CharSequence title = "Test header";
+ CarUiHeaderListItem header = new CarUiHeaderListItem(title);
+ items.add(header);
+
+ updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
+
+ CarUiListItemAdapter.HeaderViewHolder viewHolder = getHeaderViewHolderAtPosition(0);
+
+ assertThat(viewHolder.getTitle().getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(viewHolder.getTitle().getText()).isEqualTo(title);
+ assertThat(viewHolder.getBody().getVisibility()).isNotEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testHeader_titleAndBody() {
+ List<CarUiListItem> items = new ArrayList<>();
+
+ CharSequence title = "Test header";
+ CharSequence body = "With body text";
+
+ CarUiHeaderListItem header = new CarUiHeaderListItem(title, body);
+ items.add(header);
+
+ updateRecyclerViewAdapter(new CarUiListItemAdapter(items));
+
+ CarUiListItemAdapter.HeaderViewHolder viewHolder = getHeaderViewHolderAtPosition(0);
+
+ assertThat(viewHolder.getTitle().getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(viewHolder.getTitle().getText()).isEqualTo(title);
+ assertThat(viewHolder.getBody().getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(viewHolder.getBody().getText()).isEqualTo(body);
+ }
}