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