• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 BrowseSupportFragment#onCreateHeadersSupportFragment()}.
50  */
51 public class HeadersSupportFragment extends BaseRowSupportFragment {
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          */
onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row)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          */
onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row)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 
HeadersSupportFragment()92     public HeadersSupportFragment() {
93         setPresenterSelector(sHeaderPresenter);
94         FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter());
95     }
96 
setOnHeaderClickedListener(OnHeaderClickedListener listener)97     public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
98         mOnHeaderClickedListener = listener;
99     }
100 
setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener)101     public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
102         mOnHeaderViewSelectedListener = listener;
103     }
104 
105     @Override
findGridViewFromRoot(View view)106     VerticalGridView findGridViewFromRoot(View view) {
107         return (VerticalGridView) view.findViewById(R.id.browse_headers);
108     }
109 
110     @Override
onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder, int position, int subposition)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
getLayoutResourceId()158     int getLayoutResourceId() {
159         return R.layout.lb_headers_fragment;
160     }
161 
162     @Override
onViewCreated(View view, Bundle savedInstanceState)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 
updateListViewVisibility()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 
setHeadersEnabled(boolean enabled)195     void setHeadersEnabled(boolean enabled) {
196         mHeadersEnabled = enabled;
197         updateListViewVisibility();
198     }
199 
setHeadersGone(boolean gone)200     void setHeadersGone(boolean gone) {
201         mHeadersGone = gone;
202         updateListViewVisibility();
203     }
204 
205     static class NoOverlappingFrameLayout extends FrameLayout {
206 
NoOverlappingFrameLayout(Context context)207         public NoOverlappingFrameLayout(Context context) {
208             super(context);
209         }
210 
211         /**
212          * Avoid creating hardware layer for header dock.
213          */
214         @Override
hasOverlappingRendering()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
updateAdapter()234     void updateAdapter() {
235         super.updateAdapter();
236         ItemBridgeAdapter adapter = getBridgeAdapter();
237         adapter.setAdapterListener(mAdapterListener);
238         adapter.setWrapper(mWrapper);
239     }
240 
setBackgroundColor(int color)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 
updateFadingEdgeToBrandColor(int backgroundColor)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
onTransitionStart()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
onTransitionEnd()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 
isScrolling()294     public boolean isScrolling() {
295         return getVerticalGridView().getScrollState()
296                 != HorizontalGridView.SCROLL_STATE_IDLE;
297     }
298 }
299