Home | History | Annotate | Download | only in widget
      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 {
     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      */
    123     public ListItemAdapter(Context context, ListItemProvider itemProvider) {
    124         this(context, itemProvider, BackgroundStyle.SOLID);
    125     }
    126 
    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      */
    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      */
    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      */
    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
    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
    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      */
    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
    257     public int getItemViewType(int position) {
    258         return mItemProvider.get(position).getViewType();
    259     }
    260 
    261     @Override
    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
    281     public int getItemCount() {
    282         return mMaxItems == PagedListView.ItemCap.UNLIMITED
    283                 ? mItemProvider.size()
    284                 : Math.min(mItemProvider.size(), mMaxItems);
    285     }
    286 
    287     @Override
    288     public void setMaxItems(int maxItems) {
    289         mMaxItems = maxItems;
    290     }
    291 
    292     @Override
    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