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