HeadersFragment.java revision 729cbf4cd57c87bcd569db5974c8cbd51a942581
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.FocusHighlightHelper; 25import android.support.v17.leanback.widget.ItemBridgeAdapter; 26import android.support.v17.leanback.widget.PresenterSelector; 27import android.support.v17.leanback.widget.OnItemViewSelectedListener; 28import android.support.v17.leanback.widget.Row; 29import android.support.v17.leanback.widget.RowHeaderPresenter; 30import android.support.v17.leanback.widget.SinglePresenterSelector; 31import android.support.v17.leanback.widget.VerticalGridView; 32import android.support.v7.widget.RecyclerView; 33import android.util.TypedValue; 34import android.view.View; 35import android.view.ViewGroup; 36import android.view.View.OnLayoutChangeListener; 37import android.widget.FrameLayout; 38 39/** 40 * An internal fragment containing a list of row headers. 41 */ 42public class HeadersFragment extends BaseRowFragment { 43 44 /** 45 * Interface definition for a callback to be invoked when a header item is clicked. 46 */ 47 public interface OnHeaderClickedListener { 48 /** 49 * Called when a header item has been clicked. 50 * 51 * @param viewHolder Row ViewHolder object corresponding to the selected Header. 52 * @param row Row object corresponding to the selected Header. 53 */ 54 void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row); 55 } 56 57 /** 58 * Interface definition for a callback to be invoked when a header item is selected. 59 */ 60 public interface OnHeaderViewSelectedListener { 61 /** 62 * Called when a header item has been selected. 63 * 64 * @param viewHolder Row ViewHolder object corresponding to the selected Header. 65 * @param row Row object corresponding to the selected Header. 66 */ 67 void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row); 68 } 69 70 private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener; 71 private OnHeaderClickedListener mOnHeaderClickedListener; 72 private boolean mHeadersEnabled = true; 73 private boolean mHeadersGone = false; 74 private int mBackgroundColor; 75 private boolean mBackgroundColorSet; 76 77 private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector( 78 new RowHeaderPresenter(R.layout.lb_header)); 79 80 public HeadersFragment() { 81 setPresenterSelector(sHeaderPresenter); 82 } 83 84 public void setOnHeaderClickedListener(OnHeaderClickedListener listener) { 85 mOnHeaderClickedListener = listener; 86 } 87 88 public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) { 89 mOnHeaderViewSelectedListener = listener; 90 } 91 92 @Override 93 VerticalGridView findGridViewFromRoot(View view) { 94 return (VerticalGridView) view.findViewById(R.id.browse_headers); 95 } 96 97 @Override 98 void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder, 99 int position, int subposition) { 100 if (mOnHeaderViewSelectedListener != null) { 101 if (viewHolder != null && position >= 0) { 102 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder; 103 mOnHeaderViewSelectedListener.onHeaderSelected( 104 (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem()); 105 } else { 106 mOnHeaderViewSelectedListener.onHeaderSelected(null, null); 107 } 108 } 109 } 110 111 private final ItemBridgeAdapter.AdapterListener mAdapterListener = 112 new ItemBridgeAdapter.AdapterListener() { 113 @Override 114 public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) { 115 View headerView = viewHolder.getViewHolder().view; 116 headerView.setOnClickListener(new View.OnClickListener() { 117 @Override 118 public void onClick(View v) { 119 if (mOnHeaderClickedListener != null) { 120 mOnHeaderClickedListener.onHeaderClicked( 121 (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(), 122 (Row) viewHolder.getItem()); 123 } 124 } 125 }); 126 headerView.setFocusable(true); 127 headerView.setFocusableInTouchMode(true); 128 if (mWrapper != null) { 129 viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener); 130 } else { 131 headerView.addOnLayoutChangeListener(sLayoutChangeListener); 132 } 133 } 134 135 }; 136 137 private static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() { 138 @Override 139 public void onLayoutChange(View v, int left, int top, int right, int bottom, 140 int oldLeft, int oldTop, int oldRight, int oldBottom) { 141 v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0); 142 v.setPivotY(v.getMeasuredHeight() / 2); 143 } 144 }; 145 146 @Override 147 int getLayoutResourceId() { 148 return R.layout.lb_headers_fragment; 149 } 150 151 @Override 152 public void onViewCreated(View view, Bundle savedInstanceState) { 153 super.onViewCreated(view, savedInstanceState); 154 final VerticalGridView listView = getVerticalGridView(); 155 if (listView == null) { 156 return; 157 } 158 if (getBridgeAdapter() != null) { 159 FocusHighlightHelper.setupHeaderItemFocusHighlight(listView); 160 } 161 if (mBackgroundColorSet) { 162 listView.setBackgroundColor(mBackgroundColor); 163 updateFadingEdgeToBrandColor(mBackgroundColor); 164 } else { 165 Drawable d = listView.getBackground(); 166 if (d instanceof ColorDrawable) { 167 updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor()); 168 } 169 } 170 updateListViewVisibility(); 171 } 172 173 private void updateListViewVisibility() { 174 final VerticalGridView listView = getVerticalGridView(); 175 if (listView != null) { 176 getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE); 177 if (!mHeadersGone) { 178 if (mHeadersEnabled) { 179 listView.setChildrenVisibility(View.VISIBLE); 180 } else { 181 listView.setChildrenVisibility(View.INVISIBLE); 182 } 183 } 184 } 185 } 186 187 void setHeadersEnabled(boolean enabled) { 188 mHeadersEnabled = enabled; 189 updateListViewVisibility(); 190 } 191 192 void setHeadersGone(boolean gone) { 193 mHeadersGone = gone; 194 updateListViewVisibility(); 195 } 196 197 static class NoOverlappingFrameLayout extends FrameLayout { 198 199 public NoOverlappingFrameLayout(Context context) { 200 super(context); 201 } 202 203 /** 204 * Avoid creating hardware layer for header dock. 205 */ 206 @Override 207 public boolean hasOverlappingRendering() { 208 return false; 209 } 210 } 211 212 // Wrapper needed because of conflict between RecyclerView's use of alpha 213 // for ADD animations, and RowHeaderPresnter's use of alpha for selected level. 214 private final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() { 215 @Override 216 public void wrap(View wrapper, View wrapped) { 217 ((FrameLayout) wrapper).addView(wrapped); 218 } 219 220 @Override 221 public View createWrapper(View root) { 222 return new NoOverlappingFrameLayout(root.getContext()); 223 } 224 }; 225 @Override 226 void updateAdapter() { 227 super.updateAdapter(); 228 ItemBridgeAdapter adapter = getBridgeAdapter(); 229 if (adapter != null) { 230 adapter.setAdapterListener(mAdapterListener); 231 adapter.setWrapper(mWrapper); 232 } 233 if (adapter != null && getVerticalGridView() != null) { 234 FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView()); 235 } 236 } 237 238 void setBackgroundColor(int color) { 239 mBackgroundColor = color; 240 mBackgroundColorSet = true; 241 242 if (getVerticalGridView() != null) { 243 getVerticalGridView().setBackgroundColor(mBackgroundColor); 244 updateFadingEdgeToBrandColor(mBackgroundColor); 245 } 246 } 247 248 private void updateFadingEdgeToBrandColor(int backgroundColor) { 249 View fadingView = getView().findViewById(R.id.fade_out_edge); 250 Drawable background = fadingView.getBackground(); 251 if (background instanceof GradientDrawable) { 252 background.mutate(); 253 ((GradientDrawable) background).setColors( 254 new int[] {Color.TRANSPARENT, backgroundColor}); 255 } 256 } 257 258 @Override 259 void onTransitionStart() { 260 super.onTransitionStart(); 261 if (!mHeadersEnabled) { 262 // When enabling headers fragment, the RowHeaderView gets a focus but 263 // isShown() is still false because its parent is INVSIBILE, accessibility 264 // event is not sent. 265 // Workaround is: prevent focus to a child view during transition and put 266 // focus on it after transition is done. 267 final VerticalGridView listView = getVerticalGridView(); 268 if (listView != null) { 269 listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 270 if (listView.hasFocus()) { 271 listView.requestFocus(); 272 } 273 } 274 } 275 } 276 277 @Override 278 void onTransitionEnd() { 279 if (mHeadersEnabled) { 280 final VerticalGridView listView = getVerticalGridView(); 281 if (listView != null) { 282 listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 283 if (listView.hasFocus()) { 284 listView.requestFocus(); 285 } 286 } 287 } 288 super.onTransitionEnd(); 289 } 290} 291