Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package android.support.v17.leanback.app;
     16 
     17 import android.content.Context;
     18 import android.graphics.Color;
     19 import android.graphics.drawable.ColorDrawable;
     20 import android.graphics.drawable.Drawable;
     21 import android.graphics.drawable.GradientDrawable;
     22 import android.os.Bundle;
     23 import android.support.v17.leanback.R;
     24 import android.support.v17.leanback.widget.ClassPresenterSelector;
     25 import android.support.v17.leanback.widget.DividerPresenter;
     26 import android.support.v17.leanback.widget.DividerRow;
     27 import android.support.v17.leanback.widget.FocusHighlightHelper;
     28 import android.support.v17.leanback.widget.HorizontalGridView;
     29 import android.support.v17.leanback.widget.ItemBridgeAdapter;
     30 import android.support.v17.leanback.widget.PresenterSelector;
     31 import android.support.v17.leanback.widget.Row;
     32 import android.support.v17.leanback.widget.RowHeaderPresenter;
     33 import android.support.v17.leanback.widget.SectionRow;
     34 import android.support.v17.leanback.widget.VerticalGridView;
     35 import android.support.v7.widget.RecyclerView;
     36 import android.view.View;
     37 import android.view.View.OnLayoutChangeListener;
     38 import android.view.ViewGroup;
     39 import android.widget.FrameLayout;
     40 
     41 /**
     42  * An fragment containing a list of row headers. Implementation must support three types of rows:
     43  * <ul>
     44  *     <li>{@link DividerRow} rendered by {@link DividerPresenter}.</li>
     45  *     <li>{@link Row} rendered by {@link RowHeaderPresenter}.</li>
     46  *     <li>{@link SectionRow} rendered by {@link RowHeaderPresenter}.</li>
     47  * </ul>
     48  * Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
     49  * Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
     50  */
     51 public class HeadersFragment extends BaseRowFragment {
     52 
     53     /**
     54      * Interface definition for a callback to be invoked when a header item is clicked.
     55      */
     56     public interface OnHeaderClickedListener {
     57         /**
     58          * Called when a header item has been clicked.
     59          *
     60          * @param viewHolder Row ViewHolder object corresponding to the selected Header.
     61          * @param row Row object corresponding to the selected Header.
     62          */
     63         void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
     64     }
     65 
     66     /**
     67      * Interface definition for a callback to be invoked when a header item is selected.
     68      */
     69     public interface OnHeaderViewSelectedListener {
     70         /**
     71          * Called when a header item has been selected.
     72          *
     73          * @param viewHolder Row ViewHolder object corresponding to the selected Header.
     74          * @param row Row object corresponding to the selected Header.
     75          */
     76         void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
     77     }
     78 
     79     private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener;
     80     OnHeaderClickedListener mOnHeaderClickedListener;
     81     private boolean mHeadersEnabled = true;
     82     private boolean mHeadersGone = false;
     83     private int mBackgroundColor;
     84     private boolean mBackgroundColorSet;
     85 
     86     private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector()
     87             .addClassPresenter(DividerRow.class, new DividerPresenter())
     88             .addClassPresenter(SectionRow.class,
     89                     new RowHeaderPresenter(R.layout.lb_section_header, false))
     90             .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header));
     91 
     92     public HeadersFragment() {
     93         setPresenterSelector(sHeaderPresenter);
     94         FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter());
     95     }
     96 
     97     public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
     98         mOnHeaderClickedListener = listener;
     99     }
    100 
    101     public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
    102         mOnHeaderViewSelectedListener = listener;
    103     }
    104 
    105     @Override
    106     VerticalGridView findGridViewFromRoot(View view) {
    107         return (VerticalGridView) view.findViewById(R.id.browse_headers);
    108     }
    109 
    110     @Override
    111     void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
    112             int position, int subposition) {
    113         if (mOnHeaderViewSelectedListener != null) {
    114             if (viewHolder != null && position >= 0) {
    115                 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
    116                 mOnHeaderViewSelectedListener.onHeaderSelected(
    117                         (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
    118             } else {
    119                 mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
    120             }
    121         }
    122     }
    123 
    124     private final ItemBridgeAdapter.AdapterListener mAdapterListener =
    125             new ItemBridgeAdapter.AdapterListener() {
    126         @Override
    127         public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
    128             View headerView = viewHolder.getViewHolder().view;
    129             headerView.setOnClickListener(new View.OnClickListener() {
    130                 @Override
    131                 public void onClick(View v) {
    132                     if (mOnHeaderClickedListener != null) {
    133                         mOnHeaderClickedListener.onHeaderClicked(
    134                                 (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
    135                                 (Row) viewHolder.getItem());
    136                     }
    137                 }
    138             });
    139             if (mWrapper != null) {
    140                 viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
    141             } else {
    142                 headerView.addOnLayoutChangeListener(sLayoutChangeListener);
    143             }
    144         }
    145 
    146     };
    147 
    148     static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
    149         @Override
    150         public void onLayoutChange(View v, int left, int top, int right, int bottom,
    151             int oldLeft, int oldTop, int oldRight, int oldBottom) {
    152             v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
    153             v.setPivotY(v.getMeasuredHeight() / 2);
    154         }
    155     };
    156 
    157     @Override
    158     int getLayoutResourceId() {
    159         return R.layout.lb_headers_fragment;
    160     }
    161 
    162     @Override
    163     public void onViewCreated(View view, Bundle savedInstanceState) {
    164         super.onViewCreated(view, savedInstanceState);
    165         final VerticalGridView listView = getVerticalGridView();
    166         if (listView == null) {
    167             return;
    168         }
    169         if (mBackgroundColorSet) {
    170             listView.setBackgroundColor(mBackgroundColor);
    171             updateFadingEdgeToBrandColor(mBackgroundColor);
    172         } else {
    173             Drawable d = listView.getBackground();
    174             if (d instanceof ColorDrawable) {
    175                 updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor());
    176             }
    177         }
    178         updateListViewVisibility();
    179     }
    180 
    181     private void updateListViewVisibility() {
    182         final VerticalGridView listView = getVerticalGridView();
    183         if (listView != null) {
    184             getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
    185             if (!mHeadersGone) {
    186                 if (mHeadersEnabled) {
    187                     listView.setChildrenVisibility(View.VISIBLE);
    188                 } else {
    189                     listView.setChildrenVisibility(View.INVISIBLE);
    190                 }
    191             }
    192         }
    193     }
    194 
    195     void setHeadersEnabled(boolean enabled) {
    196         mHeadersEnabled = enabled;
    197         updateListViewVisibility();
    198     }
    199 
    200     void setHeadersGone(boolean gone) {
    201         mHeadersGone = gone;
    202         updateListViewVisibility();
    203     }
    204 
    205     static class NoOverlappingFrameLayout extends FrameLayout {
    206 
    207         public NoOverlappingFrameLayout(Context context) {
    208             super(context);
    209         }
    210 
    211         /**
    212          * Avoid creating hardware layer for header dock.
    213          */
    214         @Override
    215         public boolean hasOverlappingRendering() {
    216             return false;
    217         }
    218     }
    219 
    220     // Wrapper needed because of conflict between RecyclerView's use of alpha
    221     // for ADD animations, and RowHeaderPresenter's use of alpha for selected level.
    222     final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
    223         @Override
    224         public void wrap(View wrapper, View wrapped) {
    225             ((FrameLayout) wrapper).addView(wrapped);
    226         }
    227 
    228         @Override
    229         public View createWrapper(View root) {
    230             return new NoOverlappingFrameLayout(root.getContext());
    231         }
    232     };
    233     @Override
    234     void updateAdapter() {
    235         super.updateAdapter();
    236         ItemBridgeAdapter adapter = getBridgeAdapter();
    237         adapter.setAdapterListener(mAdapterListener);
    238         adapter.setWrapper(mWrapper);
    239     }
    240 
    241     void setBackgroundColor(int color) {
    242         mBackgroundColor = color;
    243         mBackgroundColorSet = true;
    244 
    245         if (getVerticalGridView() != null) {
    246             getVerticalGridView().setBackgroundColor(mBackgroundColor);
    247             updateFadingEdgeToBrandColor(mBackgroundColor);
    248         }
    249     }
    250 
    251     private void updateFadingEdgeToBrandColor(int backgroundColor) {
    252         View fadingView = getView().findViewById(R.id.fade_out_edge);
    253         Drawable background = fadingView.getBackground();
    254         if (background instanceof GradientDrawable) {
    255             background.mutate();
    256             ((GradientDrawable) background).setColors(
    257                     new int[] {Color.TRANSPARENT, backgroundColor});
    258         }
    259     }
    260 
    261     @Override
    262     public void onTransitionStart() {
    263         super.onTransitionStart();
    264         if (!mHeadersEnabled) {
    265             // When enabling headers fragment,  the RowHeaderView gets a focus but
    266             // isShown() is still false because its parent is INVISIBLE, accessibility
    267             // event is not sent.
    268             // Workaround is: prevent focus to a child view during transition and put
    269             // focus on it after transition is done.
    270             final VerticalGridView listView = getVerticalGridView();
    271             if (listView != null) {
    272                 listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    273                 if (listView.hasFocus()) {
    274                     listView.requestFocus();
    275                 }
    276             }
    277         }
    278     }
    279 
    280     @Override
    281     public void onTransitionEnd() {
    282         if (mHeadersEnabled) {
    283             final VerticalGridView listView = getVerticalGridView();
    284             if (listView != null) {
    285                 listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    286                 if (listView.hasFocus()) {
    287                     listView.requestFocus();
    288                 }
    289             }
    290         }
    291         super.onTransitionEnd();
    292     }
    293 
    294     public boolean isScrolling() {
    295         return getVerticalGridView().getScrollState()
    296                 != HorizontalGridView.SCROLL_STATE_IDLE;
    297     }
    298 }
    299