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