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