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