diff options
Diffstat (limited to 'src/com/android/tv/ui/sidepanel/SideFragment.java')
-rw-r--r-- | src/com/android/tv/ui/sidepanel/SideFragment.java | 315 |
1 files changed, 267 insertions, 48 deletions
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java index 6967a744..e7072572 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragment.java +++ b/src/com/android/tv/ui/sidepanel/SideFragment.java @@ -1,109 +1,285 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.tv.ui.sidepanel; +import android.app.Activity; import android.app.Fragment; +import android.content.Context; +import android.graphics.drawable.RippleDrawable; import android.os.Bundle; import android.support.v17.leanback.widget.VerticalGridView; import android.support.v7.widget.RecyclerView; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvActivity; +import com.android.tv.TvApplication; +import com.android.tv.analytics.DurationTimer; +import com.android.tv.analytics.HasTrackerLabel; +import com.android.tv.analytics.Tracker; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ProgramDataManager; +import com.android.tv.util.SystemProperties; import java.util.List; -public abstract class SideFragment extends Fragment { - public SideFragment() { } +public abstract class SideFragment extends Fragment implements HasTrackerLabel { + public static final int INVALID_POSITION = -1; + + private static final int RECYCLED_VIEW_POOL_SIZE = 7; + private static final int[] PRELOADED_VIEW_IDS = { + R.layout.option_item_radio_button, + R.layout.option_item_channel_lock, + R.layout.option_item_check_box, + R.layout.option_item_channel_check + }; + + private static RecyclerView.RecycledViewPool sRecycledViewPool; + + private VerticalGridView mListView; + private ItemAdapter mAdapter; + private SideFragmentListener mListener; + private ChannelDataManager mChannelDataManager; + private ProgramDataManager mProgramDataManager; + private Tracker mTracker; + private final DurationTimer mSidePanelDurationTimer = new DurationTimer(); + + private final int mHideKey; + private final int mDebugHideKey; + + public SideFragment() { + this(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_UNKNOWN); + } + + /** + * @param hideKey the KeyCode used to hide the fragment + * @param debugHideKey the KeyCode used to hide the fragment if + * {@link SystemProperties#USE_DEBUG_KEYS}. + */ + public SideFragment(int hideKey, int debugHideKey) { + mHideKey = hideKey; + mDebugHideKey = debugHideKey; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mChannelDataManager = getMainActivity().getChannelDataManager(); + mProgramDataManager = getMainActivity().getProgramDataManager(); + mTracker = ((TvApplication) getMainActivity().getApplicationContext()).getTracker(); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.option_fragment, container, false); + if (sRecycledViewPool == null) { + // sRecycledViewPool should be initialized by calling preloadRecycledViews() + // before the entering animation of this fragment starts, + // because it takes long time and if it is called after the animation starts (e.g. here) + // it can affect the animation. + throw new IllegalStateException("The RecyclerView pool has not been initialized."); + } + View view = inflater.inflate(getFragmentLayoutResourceId(), container, false); TextView textView = (TextView) view.findViewById(R.id.side_panel_title); textView.setText(getTitle()); - VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list); - listView.setAdapter(new ItemAdapter(inflater, getItemList())); - listView.requestFocus(); + mListView = (VerticalGridView) view.findViewById(R.id.side_panel_list); + mListView.setRecycledViewPool(sRecycledViewPool); - // TODO find a better way to do this - if (getFragmentManager().getBackStackEntryCount() != 0) { - view.findViewById(R.id.side_panel_shadow).setVisibility(View.GONE); - } + mAdapter = new ItemAdapter(inflater, getItemList()); + mListView.setAdapter(mAdapter); + mListView.requestFocus(); return view; } @Override + public void onResume() { + super.onResume(); + mTracker.sendShowSidePanel(this); + mTracker.sendScreenView(this.getTrackerLabel()); + mSidePanelDurationTimer.start(); + } + + @Override + public void onPause() { + mTracker.sendHideSidePanel(this, mSidePanelDurationTimer.reset()); + super.onPause(); + } + + @Override public void onDetach() { + mTracker = null; super.onDetach(); + } + + public final boolean isHideKeyForThisPanel(int keyCode) { + boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue(); + return mHideKey != KeyEvent.KEYCODE_UNKNOWN && + (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode)); + } - // TODO find a better way to do this - if (getFragmentManager().getBackStackEntryCount() == 0) { - TvActivity tvActivity = getTvActivity(); - tvActivity.onSideFragmentCanceled(BaseSideFragment.INITIATOR_UNKNOWN); - tvActivity.hideOverlays(false, false, true); + @Override + public void onDestroyView() { + super.onDestroyView(); + mListView.swapAdapter(null, true); + if (mListener != null) { + mListener.onSideFragmentViewDestroyed(); } } - protected TvActivity getTvActivity() { - return (TvActivity) getActivity(); + public final void setListener(SideFragmentListener listener) { + mListener = listener; + } + + protected void setSelectedPosition(int position) { + mListView.setSelectedPosition(position); + } + + protected int getSelectedPosition() { + return mListView.getSelectedPosition(); + } + + public void setItems(List<Item> items) { + mAdapter.reset(items); + } + + protected void closeFragment() { + getMainActivity().getOverlayManager().getSideFragmentManager().popSideFragment(); + } + + protected MainActivity getMainActivity() { + return (MainActivity) getActivity(); + } + + protected ChannelDataManager getChannelDataManager() { + return mChannelDataManager; + } + + protected ProgramDataManager getProgramDataManager() { + return mProgramDataManager; } protected void notifyDataSetChanged() { - VerticalGridView listView = (VerticalGridView) getView().findViewById(R.id.side_panel_list); - listView.getAdapter().notifyDataSetChanged(); + mAdapter.notifyDataSetChanged(); + } + + /* + * HACK: The following methods bypass the updating mechanism of RecyclerView.Adapter and + * directly updates each item. This works around a bug in the base libraries where calling + * Adapter.notifyItemsChanged() causes the VerticalGridView to lose track of displayed item + * position. + */ + + protected void notifyItemChanged(int position) { + notifyItemChanged(mAdapter.getItem(position)); + } + + protected void notifyItemChanged(Item item) { + item.notifyUpdated(); + } + + /** + * Notifies all items of ItemAdapter has changed without structural changes. + */ + protected void notifyItemsChanged() { + notifyItemsChanged(0, mAdapter.getItemCount()); + } + + /** + * Notifies some items of ItemAdapter has changed starting from position + * <code>positionStart</code> to the end without structural changes. + */ + protected void notifyItemsChanged(int positionStart) { + notifyItemsChanged(positionStart, mAdapter.getItemCount() - positionStart); + } + + protected void notifyItemsChanged(int positionStart, int itemCount) { + while (itemCount-- != 0) { + notifyItemChanged(positionStart++); + } + } + + /* + * END HACK + */ + + protected int getFragmentLayoutResourceId() { + return R.layout.option_fragment; } protected abstract String getTitle(); + @Override + public abstract String getTrackerLabel(); protected abstract List<Item> getItemList(); + public interface SideFragmentListener { + void onSideFragmentViewDestroyed(); + } + + public static void preloadRecycledViews(Context context) { + if (sRecycledViewPool != null) { + return; + } + sRecycledViewPool = new RecyclerView.RecycledViewPool(); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + for (int id : PRELOADED_VIEW_IDS) { + sRecycledViewPool.setMaxRecycledViews(id, RECYCLED_VIEW_POOL_SIZE); + for (int j = 0; j < RECYCLED_VIEW_POOL_SIZE; ++j) { + ItemAdapter.ViewHolder viewHolder = new ItemAdapter.ViewHolder( + inflater.inflate(id, null, false)); + sRecycledViewPool.putRecycledView(viewHolder); + } + } + } + private static class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> { private final LayoutInflater mLayoutInflater; - private final List<Item> mItems; + private List<Item> mItems; private ItemAdapter(LayoutInflater layoutInflater, List<Item> items) { mLayoutInflater = layoutInflater; mItems = items; } + private void reset(List<Item> items) { + mItems = items; + notifyDataSetChanged(); + } + @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = mLayoutInflater.inflate(viewType, parent, false); - final ViewHolder holder = new ViewHolder(view); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (holder.item instanceof RadioButtonItem) { - clearRadioGroup(holder.item); - } - holder.item.onSelected(); - } - }); - view.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean focusGained) { - if (focusGained) { - holder.item.onFocused(); - } - } - }); - return holder; + return new ViewHolder(mLayoutInflater.inflate(viewType, parent, false)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { - holder.item = getItem(position); - holder.item.bind(holder.itemView); + holder.onBind(this, getItem(position)); } @Override public void onViewRecycled(ViewHolder holder) { - holder.item.unbind(); - holder.item = null; + holder.onUnbind(); } @Override @@ -138,12 +314,55 @@ public abstract class SideFragment extends Fragment { } } - private static class ViewHolder extends RecyclerView.ViewHolder { - public Item item; + private static class ViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, View.OnFocusChangeListener { + private ItemAdapter mAdapter; + public Item mItem; private ViewHolder(View view) { super(view); + itemView.setOnClickListener(this); + itemView.setOnFocusChangeListener(this); + } + + public void onBind(ItemAdapter adapter, Item item) { + mAdapter = adapter; + mItem = item; + mItem.onBind(itemView); + mItem.onUpdate(); + } + + public void onUnbind() { + mItem.onUnbind(); + mItem = null; + mAdapter = null; + } + + @Override + public void onClick(View view) { + if (mItem instanceof RadioButtonItem) { + mAdapter.clearRadioGroup(mItem); + } + if (view.getBackground() instanceof RippleDrawable) { + view.postDelayed(new Runnable() { + @Override + public void run() { + if (mItem != null) { + mItem.onSelected(); + } + } + }, view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration)); + } else { + mItem.onSelected(); + } + } + + @Override + public void onFocusChange(View view, boolean focusGained) { + if (focusGained) { + mItem.onFocused(); + } } } } -}
\ No newline at end of file +} |