Home | History | Annotate | Download | only in template
      1 /*
      2  * Copyright (C) 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 com.android.setupwizardlib.template;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.drawable.Drawable;
     22 import android.os.Build;
     23 import android.os.Build.VERSION_CODES;
     24 import android.support.annotation.NonNull;
     25 import android.support.annotation.Nullable;
     26 import android.support.v7.widget.LinearLayoutManager;
     27 import android.support.v7.widget.RecyclerView;
     28 import android.support.v7.widget.RecyclerView.Adapter;
     29 import android.support.v7.widget.RecyclerView.ViewHolder;
     30 import android.util.AttributeSet;
     31 import android.view.View;
     32 
     33 import com.android.setupwizardlib.DividerItemDecoration;
     34 import com.android.setupwizardlib.R;
     35 import com.android.setupwizardlib.TemplateLayout;
     36 import com.android.setupwizardlib.items.ItemHierarchy;
     37 import com.android.setupwizardlib.items.ItemInflater;
     38 import com.android.setupwizardlib.items.RecyclerItemAdapter;
     39 import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
     40 import com.android.setupwizardlib.view.HeaderRecyclerView;
     41 import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter;
     42 
     43 /**
     44  * A {@link Mixin} for interacting with templates with recycler views. This mixin constructor takes
     45  * the instance of the recycler view to allow it to be instantiated dynamically, as in the case for
     46  * preference fragments.
     47  *
     48  * <p>Unlike typical mixins, this mixin is designed to be created in onTemplateInflated, which is
     49  * called by the super constructor, and then parse the XML attributes later in the constructor.
     50  */
     51 public class RecyclerMixin implements Mixin {
     52 
     53     private TemplateLayout mTemplateLayout;
     54 
     55     @NonNull
     56     private final RecyclerView mRecyclerView;
     57 
     58     @Nullable
     59     private View mHeader;
     60 
     61     @NonNull
     62     private DividerItemDecoration mDividerDecoration;
     63 
     64     private Drawable mDefaultDivider;
     65     private Drawable mDivider;
     66 
     67     private int mDividerInsetStart;
     68     private int mDividerInsetEnd;
     69 
     70     /**
     71      * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this
     72      * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by
     73      * the super constructor, because the recycler view and the header needs to be made available
     74      * before other mixins from the super class.
     75      *
     76      * @param layout The layout this mixin belongs to.
     77      */
     78     public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) {
     79         mTemplateLayout = layout;
     80 
     81         mDividerDecoration = new DividerItemDecoration(mTemplateLayout.getContext());
     82 
     83         // The recycler view needs to be available
     84         mRecyclerView = recyclerView;
     85         mRecyclerView.setLayoutManager(new LinearLayoutManager(mTemplateLayout.getContext()));
     86 
     87         if (recyclerView instanceof HeaderRecyclerView) {
     88             mHeader = ((HeaderRecyclerView) recyclerView).getHeader();
     89         }
     90 
     91         mRecyclerView.addItemDecoration(mDividerDecoration);
     92     }
     93 
     94     /**
     95      * Parse XML attributes and configures this mixin and the recycler view accordingly. This should
     96      * be called from the constructor of the layout.
     97      *
     98      * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the
     99      *              layout was not created from XML.
    100      * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be
    101      *                     0 if it is not needed.
    102      */
    103     public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
    104         final Context context = mTemplateLayout.getContext();
    105         final TypedArray a = context.obtainStyledAttributes(
    106                 attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0);
    107 
    108         final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0);
    109         if (entries != 0) {
    110             final ItemHierarchy inflated = new ItemInflater(context).inflate(entries);
    111             final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated);
    112             adapter.setHasStableIds(a.getBoolean(
    113                     R.styleable.SuwRecyclerMixin_suwHasStableIds, false));
    114             setAdapter(adapter);
    115         }
    116         int dividerInset =
    117                 a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, -1);
    118         if (dividerInset != -1) {
    119             setDividerInset(dividerInset);
    120         } else {
    121             int dividerInsetStart =
    122                     a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetStart, 0);
    123             int dividerInsetEnd =
    124                     a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInsetEnd, 0);
    125             setDividerInsets(dividerInsetStart, dividerInsetEnd);
    126         }
    127 
    128         a.recycle();
    129     }
    130 
    131     /**
    132      * @return The recycler view contained in the layout, as marked by
    133      *         {@code @id/suw_recycler_view}. This will return {@code null} if the recycler view
    134      *         doesn't exist in the layout.
    135      */
    136     @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler
    137                                           // view, and call this after the template is inflated,
    138                                           // this will not return null.
    139     public RecyclerView getRecyclerView() {
    140         return mRecyclerView;
    141     }
    142 
    143     /**
    144      * Gets the header view of the recycler layout. This is useful for other mixins if they need to
    145      * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}.
    146      */
    147     @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header,
    148                                           // this call will not return null.
    149     public View getHeader() {
    150         return mHeader;
    151     }
    152 
    153     /**
    154      * Recycler mixin needs to update the dividers if the layout direction has changed. This method
    155      * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template
    156      * is called.
    157      */
    158     public void onLayout() {
    159         if (mDivider == null) {
    160             // Update divider in case layout direction has just been resolved
    161             updateDivider();
    162         }
    163     }
    164 
    165     /**
    166      * Gets the adapter of the recycler view in this layout. If the adapter includes a header,
    167      * this method will unwrap it and return the underlying adapter.
    168      *
    169      * @return The adapter, or {@code null} if the recycler view has no adapter.
    170      */
    171     public Adapter<? extends ViewHolder> getAdapter() {
    172         @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :(
    173         final RecyclerView.Adapter<? extends ViewHolder> adapter = mRecyclerView.getAdapter();
    174         if (adapter instanceof HeaderAdapter) {
    175             return ((HeaderAdapter<? extends ViewHolder>) adapter).getWrappedAdapter();
    176         }
    177         return adapter;
    178     }
    179 
    180     /**
    181      * Sets the adapter on the recycler view in this layout.
    182      */
    183     public void setAdapter(Adapter<? extends ViewHolder> adapter) {
    184         mRecyclerView.setAdapter(adapter);
    185     }
    186 
    187     /**
    188      * @deprecated Use {@link #setDividerInsets(int, int)} instead.
    189      */
    190     @Deprecated
    191     public void setDividerInset(int inset) {
    192         setDividerInsets(inset, 0);
    193     }
    194 
    195     /**
    196      * Sets the start inset of the divider. This will use the default divider drawable set in the
    197      * theme and apply insets to it.
    198      *
    199      * @param start The number of pixels to inset on the "start" side of the list divider. Typically
    200      *              this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
    201      *              {@code @dimen/suw_items_glif_text_divider_inset}.
    202      * @param end The number of pixels to inset on the "end" side of the list divider.
    203      */
    204     public void setDividerInsets(int start, int end) {
    205         mDividerInsetStart = start;
    206         mDividerInsetEnd = end;
    207         updateDivider();
    208     }
    209 
    210     /**
    211      * @return The number of pixels inset on the start side of the divider.
    212      * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead.
    213      */
    214     @Deprecated
    215     public int getDividerInset() {
    216         return getDividerInsetStart();
    217     }
    218 
    219     /**
    220      * @return The number of pixels inset on the start side of the divider.
    221      */
    222     public int getDividerInsetStart() {
    223         return mDividerInsetStart;
    224     }
    225 
    226     /**
    227      * @return The number of pixels inset on the end side of the divider.
    228      */
    229     public int getDividerInsetEnd() {
    230         return mDividerInsetEnd;
    231     }
    232 
    233     private void updateDivider() {
    234         boolean shouldUpdate = true;
    235         if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
    236             shouldUpdate = mTemplateLayout.isLayoutDirectionResolved();
    237         }
    238         if (shouldUpdate) {
    239             if (mDefaultDivider == null) {
    240                 mDefaultDivider = mDividerDecoration.getDivider();
    241             }
    242             mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(
    243                     mDefaultDivider,
    244                     mDividerInsetStart /* start */,
    245                     0 /* top */,
    246                     mDividerInsetEnd /* end */,
    247                     0 /* bottom */,
    248                     mTemplateLayout);
    249             mDividerDecoration.setDivider(mDivider);
    250         }
    251     }
    252 
    253     /**
    254      * @return The drawable used as the divider.
    255      */
    256     public Drawable getDivider() {
    257         return mDivider;
    258     }
    259 
    260     /**
    261      * Sets the divider item decoration directly. This is a low level method which should be used
    262      * only if custom divider behavior is needed, for example if the divider should be shown /
    263      * hidden in some specific cases for view holders that cannot implement
    264      * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
    265      */
    266     public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) {
    267         mRecyclerView.removeItemDecoration(mDividerDecoration);
    268         mDividerDecoration = decoration;
    269         mRecyclerView.addItemDecoration(mDividerDecoration);
    270         updateDivider();
    271     }
    272 }
    273