HeadersFragment.java revision 99ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0d
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 15package android.support.v17.leanback.app; 16 17import android.content.Context; 18import android.graphics.Color; 19import android.graphics.drawable.ColorDrawable; 20import android.graphics.drawable.Drawable; 21import android.graphics.drawable.GradientDrawable; 22import android.os.Bundle; 23import android.support.v17.leanback.R; 24import android.support.v17.leanback.widget.ClassPresenterSelector; 25import android.support.v17.leanback.widget.DividerPresenter; 26import android.support.v17.leanback.widget.DividerRow; 27import android.support.v17.leanback.widget.FocusHighlightHelper; 28import android.support.v17.leanback.widget.HorizontalGridView; 29import android.support.v17.leanback.widget.ItemBridgeAdapter; 30import android.support.v17.leanback.widget.PresenterSelector; 31import android.support.v17.leanback.widget.Row; 32import android.support.v17.leanback.widget.RowHeaderPresenter; 33import android.support.v17.leanback.widget.SectionRow; 34import android.support.v17.leanback.widget.VerticalGridView; 35import android.support.v7.widget.RecyclerView; 36import android.view.View; 37import android.view.ViewGroup; 38import android.view.View.OnLayoutChangeListener; 39import android.widget.FrameLayout; 40 41/** 42 * An internal fragment containing a list of row headers. 43 */ 44public class HeadersFragment extends BaseRowFragment { 45 46 /** 47 * Interface definition for a callback to be invoked when a header item is clicked. 48 */ 49 public interface OnHeaderClickedListener { 50 /** 51 * Called when a header item has been clicked. 52 * 53 * @param viewHolder Row ViewHolder object corresponding to the selected Header. 54 * @param row Row object corresponding to the selected Header. 55 */ 56 void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row); 57 } 58 59 /** 60 * Interface definition for a callback to be invoked when a header item is selected. 61 */ 62 public interface OnHeaderViewSelectedListener { 63 /** 64 * Called when a header item has been selected. 65 * 66 * @param viewHolder Row ViewHolder object corresponding to the selected Header. 67 * @param row Row object corresponding to the selected Header. 68 */ 69 void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row); 70 } 71 72 private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener; 73 OnHeaderClickedListener mOnHeaderClickedListener; 74 private boolean mHeadersEnabled = true; 75 private boolean mHeadersGone = false; 76 private int mBackgroundColor; 77 private boolean mBackgroundColorSet; 78 79 private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector() 80 .addClassPresenter(DividerRow.class, new DividerPresenter()) 81 .addClassPresenter(SectionRow.class, 82 new RowHeaderPresenter(R.layout.lb_section_header, false)) 83 .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header)); 84 85 public HeadersFragment() { 86 setPresenterSelector(sHeaderPresenter); 87 } 88 89 public void setOnHeaderClickedListener(OnHeaderClickedListener listener) { 90 mOnHeaderClickedListener = listener; 91 } 92 93 public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) { 94 mOnHeaderViewSelectedListener = listener; 95 } 96 97 @Override 98 VerticalGridView findGridViewFromRoot(View view) { 99 return (VerticalGridView) view.findViewById(R.id.browse_headers); 100 } 101 102 @Override 103 void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder, 104 int position, int subposition) { 105 if (mOnHeaderViewSelectedListener != null) { 106 if (viewHolder != null && position >= 0) { 107 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder; 108 mOnHeaderViewSelectedListener.onHeaderSelected( 109 (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem()); 110 } else { 111 mOnHeaderViewSelectedListener.onHeaderSelected(null, null); 112 } 113 } 114 } 115 116 private final ItemBridgeAdapter.AdapterListener mAdapterListener = 117 new ItemBridgeAdapter.AdapterListener() { 118 @Override 119 public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) { 120 View headerView = viewHolder.getViewHolder().view; 121 headerView.setOnClickListener(new View.OnClickListener() { 122 @Override 123 public void onClick(View v) { 124 if (mOnHeaderClickedListener != null) { 125 mOnHeaderClickedListener.onHeaderClicked( 126 (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(), 127 (Row) viewHolder.getItem()); 128 } 129 } 130 }); 131 if (mWrapper != null) { 132 viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener); 133 } else { 134 headerView.addOnLayoutChangeListener(sLayoutChangeListener); 135 } 136 } 137 138 }; 139 140 static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() { 141 @Override 142 public void onLayoutChange(View v, int left, int top, int right, int bottom, 143 int oldLeft, int oldTop, int oldRight, int oldBottom) { 144 v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0); 145 v.setPivotY(v.getMeasuredHeight() / 2); 146 } 147 }; 148 149 @Override 150 int getLayoutResourceId() { 151 return R.layout.lb_headers_fragment; 152 } 153 154 @Override 155 public void onViewCreated(View view, Bundle savedInstanceState) { 156 super.onViewCreated(view, savedInstanceState); 157 final VerticalGridView listView = getVerticalGridView(); 158 if (listView == null) { 159 return; 160 } 161 if (getBridgeAdapter() != null) { 162 FocusHighlightHelper.setupHeaderItemFocusHighlight(listView); 163 } 164 if (mBackgroundColorSet) { 165 listView.setBackgroundColor(mBackgroundColor); 166 updateFadingEdgeToBrandColor(mBackgroundColor); 167 } else { 168 Drawable d = listView.getBackground(); 169 if (d instanceof ColorDrawable) { 170 updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor()); 171 } 172 } 173 updateListViewVisibility(); 174 } 175 176 private void updateListViewVisibility() { 177 final VerticalGridView listView = getVerticalGridView(); 178 if (listView != null) { 179 getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE); 180 if (!mHeadersGone) { 181 if (mHeadersEnabled) { 182 listView.setChildrenVisibility(View.VISIBLE); 183 } else { 184 listView.setChildrenVisibility(View.INVISIBLE); 185 } 186 } 187 } 188 } 189 190 void setHeadersEnabled(boolean enabled) { 191 mHeadersEnabled = enabled; 192 updateListViewVisibility(); 193 } 194 195 void setHeadersGone(boolean gone) { 196 mHeadersGone = gone; 197 updateListViewVisibility(); 198 } 199 200 static class NoOverlappingFrameLayout extends FrameLayout { 201 202 public NoOverlappingFrameLayout(Context context) { 203 super(context); 204 } 205 206 /** 207 * Avoid creating hardware layer for header dock. 208 */ 209 @Override 210 public boolean hasOverlappingRendering() { 211 return false; 212 } 213 } 214 215 // Wrapper needed because of conflict between RecyclerView's use of alpha 216 // for ADD animations, and RowHeaderPresenter's use of alpha for selected level. 217 final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() { 218 @Override 219 public void wrap(View wrapper, View wrapped) { 220 ((FrameLayout) wrapper).addView(wrapped); 221 } 222 223 @Override 224 public View createWrapper(View root) { 225 return new NoOverlappingFrameLayout(root.getContext()); 226 } 227 }; 228 @Override 229 void updateAdapter() { 230 super.updateAdapter(); 231 ItemBridgeAdapter adapter = getBridgeAdapter(); 232 if (adapter != null) { 233 adapter.setAdapterListener(mAdapterListener); 234 adapter.setWrapper(mWrapper); 235 } 236 if (adapter != null && getVerticalGridView() != null) { 237 FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView()); 238 } 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