1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.menu; 18 19 import android.content.Context; 20 import android.support.v17.leanback.widget.HorizontalGridView; 21 import android.support.v17.leanback.widget.OnChildSelectedListener; 22 import android.support.v7.widget.RecyclerView; 23 import android.util.AttributeSet; 24 import android.util.Log; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 29 import com.android.tv.MainActivity; 30 import com.android.tv.R; 31 import com.android.tv.util.ViewCache; 32 33 import java.util.Collections; 34 import java.util.List; 35 36 /** 37 * A view that shows a title and list view. 38 */ 39 public class ItemListRowView extends MenuRowView implements OnChildSelectedListener { 40 private static final String TAG = MenuView.TAG; 41 private static final boolean DEBUG = MenuView.DEBUG; 42 43 public interface CardView<T> { onBind(T row, boolean selected)44 void onBind(T row, boolean selected); onRecycled()45 void onRecycled(); onSelected()46 void onSelected(); onDeselected()47 void onDeselected(); 48 } 49 50 private HorizontalGridView mListView; 51 private CardView<?> mSelectedCard; 52 ItemListRowView(Context context)53 public ItemListRowView(Context context) { 54 this(context, null); 55 } 56 ItemListRowView(Context context, AttributeSet attrs)57 public ItemListRowView(Context context, AttributeSet attrs) { 58 this(context, attrs, 0); 59 } 60 ItemListRowView(Context context, AttributeSet attrs, int defStyle)61 public ItemListRowView(Context context, AttributeSet attrs, int defStyle) { 62 this(context, attrs, defStyle, 0); 63 } 64 ItemListRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)65 public ItemListRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 66 super(context, attrs, defStyleAttr, defStyleRes); 67 } 68 69 @Override onFinishInflate()70 protected void onFinishInflate() { 71 super.onFinishInflate(); 72 mListView = (HorizontalGridView) getContentsView(); 73 // Disable the position change animation of the cards. 74 mListView.setItemAnimator(null); 75 } 76 77 @Override getContentsViewId()78 protected int getContentsViewId() { 79 return R.id.list_view; 80 } 81 82 @Override onBind(MenuRow row)83 public void onBind(MenuRow row) { 84 super.onBind(row); 85 ItemListAdapter<?> adapter = ((ItemListRow) row).getAdapter(); 86 adapter.mItemListView = this; 87 88 mListView.setOnChildSelectedListener(this); 89 mListView.setAdapter(adapter); 90 } 91 92 @Override initialize(int reason)93 public void initialize(int reason) { 94 super.initialize(reason); 95 setInitialFocusView(mListView); 96 mListView.setSelectedPosition(getAdapter().getInitialPosition()); 97 } 98 getAdapter()99 private ItemListAdapter<?> getAdapter() { 100 return (ItemListAdapter<?>) mListView.getAdapter(); 101 } 102 103 @Override onChildSelected(ViewGroup parent, View child, int position, long id)104 public void onChildSelected(ViewGroup parent, View child, int position, long id) { 105 if (DEBUG) Log.d(TAG, "onChildSelected: child=" + child); 106 if (mSelectedCard == child) { 107 return; 108 } 109 if (mSelectedCard != null) { 110 mSelectedCard.onDeselected(); 111 } 112 mSelectedCard = (CardView<?>) child; 113 if (mSelectedCard != null) { 114 mSelectedCard.onSelected(); 115 } 116 } 117 118 public static abstract class ItemListAdapter<T> 119 extends RecyclerView.Adapter<ItemListAdapter.MyViewHolder> { 120 private final MainActivity mMainActivity; 121 private final LayoutInflater mLayoutInflater; 122 private List<T> mItemList = Collections.emptyList(); 123 private ItemListRowView mItemListView; 124 ItemListAdapter(Context context)125 public ItemListAdapter(Context context) { 126 // Only MainActivity can use the main menu. 127 mMainActivity = (MainActivity) context; 128 mLayoutInflater = LayoutInflater.from(context); 129 } 130 131 /** 132 * In most cases, implementation should call {@link #setItemList(java.util.List)} with 133 * newly update item list. 134 */ update()135 public abstract void update(); 136 137 /** 138 * Gets layout resource ID. It'll be used in {@link #onCreateViewHolder}. 139 */ getLayoutResId(int viewType)140 protected abstract int getLayoutResId(int viewType); 141 142 /** 143 * Releases all the resources which need to be released. 144 */ release()145 public void release() { 146 } 147 148 /** 149 * The initial position of list that will be selected when the main menu appears. 150 * By default, the first item is initially selected. 151 */ getInitialPosition()152 public int getInitialPosition() { 153 return 0; 154 } 155 156 /** The MainActivity that the corresponding ItemListView belongs to. */ getMainActivity()157 protected MainActivity getMainActivity() { 158 return mMainActivity; 159 } 160 161 /** The item list. */ getItemList()162 protected List<T> getItemList() { 163 return mItemList; 164 } 165 166 /** 167 * Sets the item list. 168 * 169 * <p>This sends an item change event, not a structural change event. The items of the same 170 * positions retain the same identity. 171 * 172 * <p>If there's any structural change and relayout and rebind is needed, call 173 * {@link #notifyDataSetChanged} explicitly. 174 */ setItemList(List<T> itemList)175 protected void setItemList(List<T> itemList) { 176 int oldSize = mItemList.size(); 177 int newSize = itemList.size(); 178 mItemList = itemList; 179 if (oldSize > newSize) { 180 notifyItemRangeChanged(0, newSize); 181 notifyItemRangeRemoved(newSize, oldSize - newSize); 182 } else if (oldSize < newSize) { 183 notifyItemRangeChanged(0, oldSize); 184 notifyItemRangeInserted(oldSize, newSize - oldSize); 185 } else { 186 notifyItemRangeChanged(0, oldSize); 187 } 188 } 189 190 @Override getItemViewType(int position)191 public int getItemViewType(int position) { 192 return 0; 193 } 194 195 @Override getItemCount()196 public int getItemCount() { 197 return mItemList.size(); 198 } 199 200 /** 201 * Returns the position of the item. 202 */ getItemPosition(T item)203 protected int getItemPosition(T item) { 204 return mItemList.indexOf(item); 205 } 206 207 /** 208 * Returns {@code true} if the item list contains the item, otherwise {@code false}. 209 */ containsItem(T item)210 protected boolean containsItem(T item) { 211 return mItemList.contains(item); 212 } 213 214 @Override onCreateViewHolder(ViewGroup parent, int viewType)215 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 216 View view = ViewCache.getInstance().getOrCreateView( 217 mLayoutInflater, getLayoutResId(viewType), parent); 218 return new MyViewHolder(view); 219 } 220 221 @Override onBindViewHolder(MyViewHolder viewHolder, int position)222 public void onBindViewHolder(MyViewHolder viewHolder, int position) { 223 @SuppressWarnings("unchecked") 224 CardView<T> cardView = (CardView<T>) viewHolder.itemView; 225 cardView.onBind(mItemList.get(position), cardView.equals(mItemListView.mSelectedCard)); 226 } 227 228 @Override onViewRecycled(MyViewHolder viewHolder)229 public void onViewRecycled(MyViewHolder viewHolder) { 230 super.onViewRecycled(viewHolder); 231 CardView<T> cardView = (CardView<T>) viewHolder.itemView; 232 cardView.onRecycled(); 233 } 234 235 public static class MyViewHolder extends RecyclerView.ViewHolder { MyViewHolder(View view)236 public MyViewHolder(View view) { 237 super(view); 238 } 239 } 240 } 241 } 242