1 /* 2 * Copyright 2017 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 androidx.car.widget; 18 19 import android.app.Activity; 20 import android.car.drivingstate.CarUxRestrictions; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.os.Bundle; 24 import android.util.SparseArray; 25 import android.util.SparseIntArray; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.FrameLayout; 30 31 import androidx.annotation.ColorInt; 32 import androidx.annotation.IntDef; 33 import androidx.annotation.LayoutRes; 34 import androidx.annotation.StyleRes; 35 import androidx.car.R; 36 import androidx.car.utils.CarUxRestrictionsHelper; 37 import androidx.car.utils.ListItemBackgroundResolver; 38 import androidx.cardview.widget.CardView; 39 import androidx.recyclerview.widget.RecyclerView; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.function.Function; 44 45 /** 46 * Adapter for {@link PagedListView} to display {@link ListItem}. 47 * 48 * <ul> 49 * <li> Implements {@link PagedListView.ItemCap} - defaults to unlimited item count. 50 * <li> Implements {@link PagedListView.DividerVisibilityManager} - to control dividers after 51 * individual {@link ListItem}. 52 * </ul> 53 * 54 * <p>To enable support for {@link CarUxRestrictions}, call {@link #start()} in your 55 * {@code Activity}'s {@link android.app.Activity#onCreate(Bundle)}, and {@link #stop()} in 56 * {@link Activity#onStop()}. 57 */ 58 public class ListItemAdapter extends 59 RecyclerView.Adapter<ListItem.ViewHolder> implements PagedListView.ItemCap, 60 PagedListView.DividerVisibilityManager { 61 62 /** 63 * Constant class for background style of items. 64 */ 65 public static final class BackgroundStyle { BackgroundStyle()66 private BackgroundStyle() {} 67 68 /** 69 * Sets the background color of each item. 70 * Background can be configured by {@link R.styleable#ListItem_listItemBackgroundColor}. 71 */ 72 public static final int SOLID = 0; 73 /** 74 * Sets the background color of each item to none (transparent). 75 */ 76 public static final int NONE = 1; 77 /** 78 * Sets each item in {@link CardView} with a rounded corner background and shadow. 79 */ 80 public static final int CARD = 2; 81 /** 82 * Sets background of each item so the combined list looks like one elongated card, namely 83 * top and bottom item will have rounded corner at only top/bottom side respectively. If 84 * only one item exists, it will have both top and bottom rounded corner. 85 */ 86 public static final int PANEL = 3; 87 } 88 89 @Retention(RetentionPolicy.SOURCE) 90 @IntDef({ 91 BackgroundStyle.SOLID, 92 BackgroundStyle.NONE, 93 BackgroundStyle.CARD, 94 BackgroundStyle.PANEL 95 }) 96 private @interface ListBackgroundStyle {} 97 98 static final int LIST_ITEM_TYPE_TEXT = 1; 99 static final int LIST_ITEM_TYPE_SEEKBAR = 2; 100 static final int LIST_ITEM_TYPE_SUBHEADER = 3; 101 102 private final SparseIntArray mViewHolderLayoutResIds = new SparseIntArray(); 103 private final SparseArray<Function<View, ListItem.ViewHolder>> mViewHolderCreator = 104 new SparseArray<>(); 105 106 @ListBackgroundStyle private int mBackgroundStyle; 107 108 @ColorInt private int mListItemBackgroundColor; 109 @StyleRes private int mListItemTitleTextAppearance; 110 @StyleRes private int mListItemBodyTextAppearance; 111 112 private final CarUxRestrictionsHelper mUxRestrictionsHelper; 113 private CarUxRestrictions mCurrentUxRestrictions; 114 115 private Context mContext; 116 private final ListItemProvider mItemProvider; 117 118 private int mMaxItems = PagedListView.ItemCap.UNLIMITED; 119 120 /** 121 * Defaults {@link BackgroundStyle} to {@link BackgroundStyle#SOLID}. 122 */ ListItemAdapter(Context context, ListItemProvider itemProvider)123 public ListItemAdapter(Context context, ListItemProvider itemProvider) { 124 this(context, itemProvider, BackgroundStyle.SOLID); 125 } 126 ListItemAdapter(Context context, ListItemProvider itemProvider, @ListBackgroundStyle int backgroundStyle)127 public ListItemAdapter(Context context, ListItemProvider itemProvider, 128 @ListBackgroundStyle int backgroundStyle) { 129 mContext = context; 130 mItemProvider = itemProvider; 131 mBackgroundStyle = backgroundStyle; 132 133 registerListItemViewType(LIST_ITEM_TYPE_TEXT, 134 R.layout.car_list_item_text_content, TextListItem::createViewHolder); 135 registerListItemViewType(LIST_ITEM_TYPE_SEEKBAR, 136 R.layout.car_list_item_seekbar_content, SeekbarListItem::createViewHolder); 137 registerListItemViewType(LIST_ITEM_TYPE_SUBHEADER, 138 R.layout.car_list_item_subheader_content, SubheaderListItem::createViewHolder); 139 140 mUxRestrictionsHelper = 141 new CarUxRestrictionsHelper(context, carUxRestrictions -> { 142 mCurrentUxRestrictions = carUxRestrictions; 143 notifyDataSetChanged(); 144 }); 145 } 146 147 /** 148 * Enables support for {@link CarUxRestrictions}. 149 * 150 * <p>This method can be called from {@code Activity}'s {@link Activity#onStart()}, or at the 151 * time of construction. 152 * 153 * <p>This method must be accompanied with a matching {@link #stop()} to avoid leak. 154 */ start()155 public void start() { 156 mUxRestrictionsHelper.start(); 157 } 158 159 /** 160 * Disables support for {@link CarUxRestrictions}, and frees up resources. 161 * 162 * <p>This method should be called from {@code Activity}'s {@link Activity#onStop()}, or at the 163 * time of this adapter being discarded. 164 */ stop()165 public void stop() { 166 mUxRestrictionsHelper.stop(); 167 } 168 169 /** 170 * Registers a function that returns {@link RecyclerView.ViewHolder} 171 * for its matching view type returned by {@link ListItem#getViewType()}. 172 * 173 * <p>The function will receive a view as {@link RecyclerView.ViewHolder#itemView}. This view 174 * uses background defined by {@link BackgroundStyle}. 175 * 176 * <p>Subclasses of {@link ListItem} in package androidx.car.widget are already registered. 177 * 178 * @param viewType use negative value for custom view type. 179 * @param function function to create ViewHolder for {@code viewType}. 180 */ registerListItemViewType(int viewType, @LayoutRes int layoutResId, Function<View, ListItem.ViewHolder> function)181 public void registerListItemViewType(int viewType, @LayoutRes int layoutResId, 182 Function<View, ListItem.ViewHolder> function) { 183 if (mViewHolderLayoutResIds.get(viewType) != 0 184 || mViewHolderCreator.get(viewType) != null) { 185 throw new IllegalArgumentException("View type is already registered."); 186 } 187 mViewHolderCreator.put(viewType, function); 188 mViewHolderLayoutResIds.put(viewType, layoutResId); 189 } 190 191 @Override onAttachedToRecyclerView(RecyclerView recyclerView)192 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 193 super.onAttachedToRecyclerView(recyclerView); 194 // When attached to the RecyclerView, update the Context so that this ListItemAdapter can 195 // retrieve theme information off that view. 196 mContext = recyclerView.getContext(); 197 198 TypedArray a = mContext.getTheme().obtainStyledAttributes(R.styleable.ListItem); 199 200 mListItemBackgroundColor = a.getColor(R.styleable.ListItem_listItemBackgroundColor, 201 mContext.getColor(R.color.car_card)); 202 mListItemTitleTextAppearance = a.getResourceId( 203 R.styleable.ListItem_listItemTitleTextAppearance, 204 R.style.TextAppearance_Car_Body1); 205 mListItemBodyTextAppearance = a.getResourceId( 206 R.styleable.ListItem_listItemBodyTextAppearance, 207 R.style.TextAppearance_Car_Body2); 208 a.recycle(); 209 } 210 211 @Override onCreateViewHolder(ViewGroup parent, int viewType)212 public ListItem.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 213 if (mViewHolderLayoutResIds.get(viewType) == 0 214 || mViewHolderCreator.get(viewType) == null) { 215 throw new IllegalArgumentException("Unregistered view type."); 216 } 217 218 LayoutInflater inflater = LayoutInflater.from(mContext); 219 View itemView = inflater.inflate(mViewHolderLayoutResIds.get(viewType), parent, false); 220 221 ViewGroup container = createListItemContainer(); 222 container.addView(itemView); 223 return mViewHolderCreator.get(viewType).apply(container); 224 } 225 226 /** 227 * Creates a view with background set by {@link BackgroundStyle}. 228 */ createListItemContainer()229 private ViewGroup createListItemContainer() { 230 ViewGroup container; 231 if (mBackgroundStyle == BackgroundStyle.CARD) { 232 CardView card = new CardView(mContext); 233 RecyclerView.LayoutParams cardLayoutParams = new RecyclerView.LayoutParams( 234 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 235 cardLayoutParams.bottomMargin = mContext.getResources().getDimensionPixelSize( 236 R.dimen.car_padding_1); 237 card.setLayoutParams(cardLayoutParams); 238 card.setRadius(mContext.getResources().getDimensionPixelSize(R.dimen.car_radius_1)); 239 card.setCardBackgroundColor(mListItemBackgroundColor); 240 241 container = card; 242 } else { 243 FrameLayout frameLayout = new FrameLayout(mContext); 244 frameLayout.setLayoutParams(new RecyclerView.LayoutParams( 245 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 246 // Skip setting background color for NONE. 247 if (mBackgroundStyle != BackgroundStyle.NONE) { 248 frameLayout.setBackgroundColor(mListItemBackgroundColor); 249 } 250 251 container = frameLayout; 252 } 253 return container; 254 } 255 256 @Override getItemViewType(int position)257 public int getItemViewType(int position) { 258 return mItemProvider.get(position).getViewType(); 259 } 260 261 @Override onBindViewHolder(ListItem.ViewHolder holder, int position)262 public void onBindViewHolder(ListItem.ViewHolder holder, int position) { 263 if (mBackgroundStyle == BackgroundStyle.PANEL) { 264 ListItemBackgroundResolver.setBackground( 265 holder.itemView, position, mItemProvider.size()); 266 } 267 268 // Car may not be initialized thus current UXR will not be available. 269 if (mCurrentUxRestrictions != null) { 270 holder.complyWithUxRestrictions(mCurrentUxRestrictions); 271 } 272 273 ListItem item = mItemProvider.get(position); 274 item.setTitleTextAppearance(mListItemTitleTextAppearance); 275 item.setBodyTextAppearance(mListItemBodyTextAppearance); 276 277 item.bind(holder); 278 } 279 280 @Override getItemCount()281 public int getItemCount() { 282 return mMaxItems == PagedListView.ItemCap.UNLIMITED 283 ? mItemProvider.size() 284 : Math.min(mItemProvider.size(), mMaxItems); 285 } 286 287 @Override setMaxItems(int maxItems)288 public void setMaxItems(int maxItems) { 289 mMaxItems = maxItems; 290 } 291 292 @Override shouldHideDivider(int position)293 public boolean shouldHideDivider(int position) { 294 // By default we should show the divider i.e. return false. 295 296 // Check if position is within range, and then check the item flag. 297 return position >= 0 && position < getItemCount() 298 && mItemProvider.get(position).shouldHideDivider(); 299 } 300 } 301