Home | History | Annotate | Download | only in items
      1 /*
      2  * Copyright (C) 2015 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 com.android.setupwizardlib.items;
     18 
     19 import android.content.res.TypedArray;
     20 import android.graphics.Rect;
     21 import android.graphics.drawable.Drawable;
     22 import android.graphics.drawable.LayerDrawable;
     23 import android.support.annotation.VisibleForTesting;
     24 import android.support.v7.widget.RecyclerView;
     25 import android.util.Log;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 
     30 import com.android.setupwizardlib.R;
     31 
     32 /**
     33  * An adapter used with RecyclerView to display an {@link ItemHierarchy}. The item hierarchy used to
     34  * create this adapter can be inflated by {@link com.android.setupwizardlib.items.ItemInflater} from
     35  * XML.
     36  */
     37 public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder>
     38         implements ItemHierarchy.Observer {
     39 
     40     private static final String TAG = "RecyclerItemAdapter";
     41 
     42     /**
     43      * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will
     44      * not create the default background for the list item. This means the item will not have ripple
     45      * touch feedback by default.
     46      */
     47     public static final String TAG_NO_BACKGROUND = "noBackground";
     48 
     49     public interface OnItemSelectedListener {
     50         void onItemSelected(IItem item);
     51     }
     52 
     53     private final ItemHierarchy mItemHierarchy;
     54     private OnItemSelectedListener mListener;
     55 
     56     public RecyclerItemAdapter(ItemHierarchy hierarchy) {
     57         mItemHierarchy = hierarchy;
     58         mItemHierarchy.registerObserver(this);
     59     }
     60 
     61     public IItem getItem(int position) {
     62         return mItemHierarchy.getItemAt(position);
     63     }
     64 
     65     @Override
     66     public long getItemId(int position) {
     67         IItem mItem = getItem(position);
     68         if (mItem instanceof AbstractItem) {
     69             final int id = ((AbstractItem) mItem).getId();
     70             return id > 0 ? id : RecyclerView.NO_ID;
     71         } else {
     72             return RecyclerView.NO_ID;
     73         }
     74     }
     75 
     76     @Override
     77     public int getItemCount() {
     78         return mItemHierarchy.getCount();
     79     }
     80 
     81     @Override
     82     public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     83         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
     84         final View view = inflater.inflate(viewType, parent, false);
     85         final ItemViewHolder viewHolder = new ItemViewHolder(view);
     86 
     87         final Object viewTag = view.getTag();
     88         if (!TAG_NO_BACKGROUND.equals(viewTag)) {
     89             final TypedArray typedArray = parent.getContext()
     90                     .obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter);
     91             Drawable selectableItemBackground = typedArray.getDrawable(
     92                     R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground);
     93             if (selectableItemBackground == null) {
     94                 selectableItemBackground = typedArray.getDrawable(
     95                         R.styleable.SuwRecyclerItemAdapter_selectableItemBackground);
     96             }
     97 
     98             final Drawable background = typedArray.getDrawable(
     99                     R.styleable.SuwRecyclerItemAdapter_android_colorBackground);
    100 
    101             if (selectableItemBackground == null || background == null) {
    102                 Log.e(TAG, "Cannot resolve required attributes."
    103                         + " selectableItemBackground=" + selectableItemBackground
    104                         + " background=" + background);
    105             } else {
    106                 final Drawable[] layers = {background, selectableItemBackground};
    107                 view.setBackgroundDrawable(new PatchedLayerDrawable(layers));
    108             }
    109 
    110             typedArray.recycle();
    111         }
    112 
    113         view.setOnClickListener(new View.OnClickListener() {
    114             @Override
    115             public void onClick(View view) {
    116                 final IItem item = viewHolder.getItem();
    117                 if (mListener != null && item != null && item.isEnabled()) {
    118                     mListener.onItemSelected(item);
    119                 }
    120             }
    121         });
    122 
    123         return viewHolder;
    124     }
    125 
    126     @Override
    127     public void onBindViewHolder(ItemViewHolder holder, int position) {
    128         final IItem item = getItem(position);
    129         item.onBindView(holder.itemView);
    130         holder.setEnabled(item.isEnabled());
    131         holder.setItem(item);
    132     }
    133 
    134     @Override
    135     public int getItemViewType(int position) {
    136         // Use layout resource as item view type. RecyclerView item type does not have to be
    137         // contiguous.
    138         IItem item = getItem(position);
    139         return item.getLayoutResource();
    140     }
    141 
    142     @Override
    143     public void onChanged(ItemHierarchy hierarchy) {
    144         notifyDataSetChanged();
    145     }
    146 
    147     @Override
    148     public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
    149         notifyItemRangeChanged(positionStart, itemCount);
    150     }
    151 
    152     @Override
    153     public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
    154         notifyItemRangeInserted(positionStart, itemCount);
    155     }
    156 
    157     @Override
    158     public void onItemRangeMoved(ItemHierarchy itemHierarchy, int fromPosition, int toPosition,
    159             int itemCount) {
    160         // There is no notifyItemRangeMoved
    161         // https://code.google.com/p/android/issues/detail?id=125984
    162         if (itemCount == 1) {
    163             notifyItemMoved(fromPosition, toPosition);
    164         } else {
    165             // If more than one, degenerate into the catch-all data set changed callback, since I'm
    166             // not sure how recycler view handles multiple calls to notifyItemMoved (if the result
    167             // is committed after every notification then naively calling
    168             // notifyItemMoved(from + i, to + i) is wrong).
    169             // Logging this in case this is a more common occurrence than expected.
    170             Log.i(TAG, "onItemRangeMoved with more than one item");
    171             notifyDataSetChanged();
    172         }
    173     }
    174 
    175     @Override
    176     public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
    177         notifyItemRangeRemoved(positionStart, itemCount);
    178     }
    179 
    180     public ItemHierarchy findItemById(int id) {
    181         return mItemHierarchy.findItemById(id);
    182     }
    183 
    184     public ItemHierarchy getRootItemHierarchy() {
    185         return mItemHierarchy;
    186     }
    187 
    188     public void setOnItemSelectedListener(OnItemSelectedListener listener) {
    189         mListener = listener;
    190     }
    191 
    192     /**
    193      * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers
    194      * do not have any padding. Patch the implementation so that getPadding returns false if the
    195      * padding is empty.
    196      *
    197      * When getPadding is true, the padding of the view will be replaced by the padding of the
    198      * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class
    199      * makes sure layer drawables without padding does not clear out original padding on the view.
    200      */
    201     @VisibleForTesting
    202     static class PatchedLayerDrawable extends LayerDrawable {
    203 
    204         /**
    205          * {@inheritDoc}
    206          */
    207         PatchedLayerDrawable(Drawable[] layers) {
    208             super(layers);
    209         }
    210 
    211         @Override
    212         public boolean getPadding(Rect padding) {
    213             final boolean superHasPadding = super.getPadding(padding);
    214             return superHasPadding
    215                     && !(padding.left == 0
    216                             && padding.top == 0
    217                             && padding.right == 0
    218                             && padding.bottom == 0);
    219         }
    220     }
    221 }
    222