GridActivity.java revision 89b4aed6fcd80f940531e265936ab908a8037f87
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
17package android.support.v17.leanback.widget;
18
19import android.support.v17.leanback.tests.R;
20import android.support.v7.widget.RecyclerView;
21import android.support.v17.leanback.widget.BaseGridView;
22import android.support.v17.leanback.widget.OnChildSelectedListener;
23import android.app.Activity;
24import android.content.Intent;
25import android.graphics.Color;
26import android.os.Bundle;
27import android.util.Log;
28import android.util.SparseArray;
29import android.view.View;
30import android.view.View.OnClickListener;
31import android.view.View.OnFocusChangeListener;
32import android.view.ViewGroup;
33import android.widget.ImageView;
34import android.widget.TextView;
35
36import java.util.ArrayList;
37
38/**
39 * @hide from javadoc
40 */
41public class GridActivity extends Activity {
42
43    private static final String TAG = "GridActivity";
44
45    public static final String EXTRA_LAYOUT_RESOURCE_ID = "layoutResourceId";
46    public static final String EXTRA_NUM_ITEMS = "numItems";
47    public static final String EXTRA_ITEMS = "items";
48    public static final String EXTRA_ITEMS_FOCUSABLE = "itemsFocusable";
49    public static final String EXTRA_STAGGERED = "staggered";
50    public static final String EXTRA_REQUEST_LAYOUT_ONFOCUS = "requestLayoutOnFocus";
51    public static final String EXTRA_REQUEST_FOCUS_ONLAYOUT = "requstFocusOnLayout";
52    public static final String EXTRA_CHILD_LAYOUT_ID = "childLayoutId";
53    public static final String EXTRA_SECONDARY_SIZE_ZERO = "secondarySizeZero";
54    /**
55     * Class that implements GridWidgetTest.ViewTypeProvider for creating different
56     * view types for each position.
57     */
58    public static final String EXTRA_VIEWTYPEPROVIDER_CLASS = "viewtype_class";
59    /**
60     * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
61     * ItemAlignmentFacet for each ViewHolder.
62     */
63    public static final String EXTRA_ITEMALIGNMENTPROVIDER_CLASS = "itemalignment_class";
64    /**
65     * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
66     * ItemAlignmentFacet for a given viewType.
67     */
68    public static final String EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS =
69            "itemalignment_viewtype_class";
70    public static final String SELECT_ACTION = "android.test.leanback.widget.SELECT";
71
72    static final int DEFAULT_NUM_ITEMS = 100;
73    static final boolean DEFAULT_STAGGERED = true;
74    static final boolean DEFAULT_REQUEST_LAYOUT_ONFOCUS = false;
75    static final boolean DEFAULT_REQUEST_FOCUS_ONLAYOUT = false;
76
77    private static final boolean DEBUG = false;
78
79    int mLayoutId;
80    int mOrientation;
81    int mNumItems;
82    int mChildLayout;
83    boolean mStaggered;
84    boolean mRequestLayoutOnFocus;
85    boolean mRequestFocusOnLayout;
86    boolean mSecondarySizeZero;
87    GridWidgetTest.ViewTypeProvider mViewTypeProvider;
88    GridWidgetTest.ItemAlignmentFacetProvider mAlignmentProvider;
89    GridWidgetTest.ItemAlignmentFacetProvider mAlignmentViewTypeProvider;
90
91    int[] mGridViewLayoutSize;
92    BaseGridView mGridView;
93    int[] mItemLengths;
94    boolean[] mItemFocusables;
95
96    private int mBoundCount;
97
98    private View createView() {
99
100        View view = getLayoutInflater().inflate(mLayoutId, null, false);
101        mGridView = (BaseGridView) view.findViewById(R.id.gridview);
102        mOrientation = mGridView instanceof HorizontalGridView ? BaseGridView.HORIZONTAL :
103                BaseGridView.VERTICAL;
104        mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
105        mGridView.setWindowAlignmentOffsetPercent(35);
106        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
107            @Override
108            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
109                if (DEBUG) Log.d(TAG, "onChildSelected position=" + position +  " id="+id);
110            }
111        });
112        return view;
113    }
114
115    @Override
116    protected void onCreate(Bundle savedInstanceState) {
117        Intent intent = getIntent();
118
119        mLayoutId = intent.getIntExtra(EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
120        mChildLayout = intent.getIntExtra(EXTRA_CHILD_LAYOUT_ID, -1);
121        mStaggered = intent.getBooleanExtra(EXTRA_STAGGERED, DEFAULT_STAGGERED);
122        mRequestLayoutOnFocus = intent.getBooleanExtra(EXTRA_REQUEST_LAYOUT_ONFOCUS,
123                DEFAULT_REQUEST_LAYOUT_ONFOCUS);
124        mRequestFocusOnLayout = intent.getBooleanExtra(EXTRA_REQUEST_FOCUS_ONLAYOUT,
125                DEFAULT_REQUEST_FOCUS_ONLAYOUT);
126        mSecondarySizeZero = intent.getBooleanExtra(EXTRA_SECONDARY_SIZE_ZERO, false);
127        mItemLengths = intent.getIntArrayExtra(EXTRA_ITEMS);
128        mItemFocusables = intent.getBooleanArrayExtra(EXTRA_ITEMS_FOCUSABLE);
129        String alignmentClass = intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_CLASS);
130        String alignmentViewTypeClass =
131                intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS);
132        String viewTypeClass = intent.getStringExtra(EXTRA_VIEWTYPEPROVIDER_CLASS);
133        try {
134            if (alignmentClass != null) {
135                mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
136                        Class.forName(alignmentClass).newInstance();
137            }
138            if (alignmentViewTypeClass != null) {
139                mAlignmentViewTypeProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
140                        Class.forName(alignmentViewTypeClass).newInstance();
141            }
142            if (viewTypeClass != null) {
143                mViewTypeProvider = (GridWidgetTest.ViewTypeProvider)
144                        Class.forName(viewTypeClass).newInstance();
145            }
146        } catch (ClassNotFoundException ex) {
147            throw new RuntimeException(ex);
148        } catch (InstantiationException ex) {
149            throw new RuntimeException(ex);
150        } catch (IllegalAccessException ex) {
151            throw new RuntimeException(ex);
152        }
153
154        super.onCreate(savedInstanceState);
155
156        if (DEBUG) Log.v(TAG, "onCreate " + this);
157
158        RecyclerView.Adapter adapter = new MyAdapter();
159
160        View view = createView();
161        if (mItemLengths == null) {
162            mNumItems = intent.getIntExtra(EXTRA_NUM_ITEMS, DEFAULT_NUM_ITEMS);
163            mItemLengths = new int[mNumItems];
164            for (int i = 0; i < mItemLengths.length; i++) {
165                if (mOrientation == BaseGridView.HORIZONTAL) {
166                    mItemLengths[i] = mStaggered ? (int)(Math.random() * 180) + 180 : 240;
167                } else {
168                    mItemLengths[i] = mStaggered ? (int)(Math.random() * 120) + 120 : 160;
169                }
170            }
171        } else {
172            mNumItems = mItemLengths.length;
173        }
174
175        mGridView.setAdapter(new MyAdapter());
176        setContentView(view);
177    }
178
179    void rebindToNewAdapter() {
180        mGridView.setAdapter(new MyAdapter());
181    }
182
183    @Override
184    protected void onNewIntent(Intent intent) {
185        if (DEBUG) Log.v(TAG, "onNewIntent " + intent+ " "+this);
186        if (intent.getAction().equals(SELECT_ACTION)) {
187            int position = intent.getIntExtra("SELECT_POSITION", -1);
188            if (position >= 0) {
189                mGridView.setSelectedPosition(position);
190            }
191        }
192        super.onNewIntent(intent);
193    }
194
195    private OnFocusChangeListener mItemFocusChangeListener = new OnFocusChangeListener() {
196
197        @Override
198        public void onFocusChange(View v, boolean hasFocus) {
199            if (hasFocus) {
200                v.setBackgroundColor(Color.YELLOW);
201            } else {
202                v.setBackgroundColor(Color.LTGRAY);
203            }
204            if (mRequestLayoutOnFocus) {
205                RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
206                int position = vh.getAdapterPosition();
207                updateSize(v, position);
208            }
209        }
210    };
211
212    private OnFocusChangeListener mSubItemFocusChangeListener = new OnFocusChangeListener() {
213
214        @Override
215        public void onFocusChange(View v, boolean hasFocus) {
216            if (hasFocus) {
217                v.setBackgroundColor(Color.YELLOW);
218            } else {
219                v.setBackgroundColor(Color.LTGRAY);
220            }
221        }
222    };
223
224    void resetBoundCount() {
225        mBoundCount = 0;
226    }
227
228    int getBoundCount() {
229       return mBoundCount;
230    }
231
232    void swap(int index1, int index2) {
233        if (index1 == index2) {
234            return;
235        } else if (index1 > index2) {
236            int index = index1;
237            index1 = index2;
238            index2 = index;
239        }
240        int value = mItemLengths[index1];
241        mItemLengths[index1] = mItemLengths[index2];
242        mItemLengths[index2] = value;
243        mGridView.getAdapter().notifyItemMoved(index1, index2);
244        mGridView.getAdapter().notifyItemMoved(index2 - 1, index1);
245    }
246
247    void changeArraySize(int length) {
248        mNumItems = length;
249        mGridView.getAdapter().notifyDataSetChanged();
250    }
251
252    int[] removeItems(int index, int length) {
253        int[] removed = new int[length];
254        System.arraycopy(mItemLengths, index, removed, 0, length);
255        System.arraycopy(mItemLengths, index + length, mItemLengths, index,
256                mNumItems - index - length);
257        mNumItems -= length;
258        mGridView.getAdapter().notifyItemRangeRemoved(index, length);
259        return removed;
260    }
261
262    void addItems(int index, int[] items) {
263        int length = items.length;
264        if (mItemLengths.length < mNumItems + length) {
265            int[] array = new int[mNumItems + length];
266            System.arraycopy(mItemLengths, 0, array, 0, mNumItems);
267            mItemLengths = array;
268        }
269        System.arraycopy(mItemLengths, index, mItemLengths, index + length, mNumItems - index);
270        System.arraycopy(items, 0, mItemLengths, index, length);
271        mNumItems += length;
272        mGridView.getAdapter().notifyItemRangeInserted(index, length);
273    }
274
275    class MyAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
276
277        @Override
278        public int getItemViewType(int position) {
279            if (mViewTypeProvider != null) {
280                return mViewTypeProvider.getViewType(position);
281            }
282            return 0;
283        }
284
285        @Override
286        public FacetProvider getFacetProvider(int viewType) {
287            final Object alignmentFacet = mAlignmentViewTypeProvider != null?
288                mAlignmentViewTypeProvider.getItemAlignmentFacet(viewType) : null;
289            if (alignmentFacet != null) {
290                return new FacetProvider() {
291                    @Override
292                    public Object getFacet(Class facetClass) {
293                        if (facetClass.equals(ItemAlignmentFacet.class)) {
294                            return alignmentFacet;
295                        }
296                        return null;
297                    }
298                };
299            }
300            return null;
301        }
302
303        @Override
304        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
305            if (DEBUG) Log.v(TAG, "createViewHolder " + viewType);
306            if (mChildLayout != -1) {
307                final View view = getLayoutInflater().inflate(mChildLayout, null, false);
308                ArrayList<View> focusables = new ArrayList<View>();
309                view.addFocusables(focusables, View.FOCUS_UP);
310                for (int i = 0; i < focusables.size(); i++) {
311                    View f = focusables.get(i);
312                    f.setBackgroundColor(Color.LTGRAY);
313                    f.setOnFocusChangeListener(new OnFocusChangeListener() {
314                        @Override
315                        public void onFocusChange(View v, boolean hasFocus) {
316                            if (hasFocus) {
317                                v.setBackgroundColor(Color.YELLOW);
318                            } else {
319                                v.setBackgroundColor(Color.LTGRAY);
320                            }
321                            if (mRequestLayoutOnFocus) {
322                                if (v == view) {
323                                    RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
324                                    int position = vh.getAdapterPosition();
325                                    updateSize(v, position);
326                                }
327                                view.requestLayout();
328                            }
329                        }
330                    });
331                }
332                ViewHolder holder = new ViewHolder(view);
333                return holder;
334            }
335            TextView textView = new TextView(parent.getContext()) {
336                @Override
337                protected void onLayout(boolean change, int left, int top, int right, int bottom) {
338                    super.onLayout(change, left, top, right, bottom);
339                    if (mRequestFocusOnLayout) {
340                        if (hasFocus()) {
341                            clearFocus();
342                            requestFocus();
343                        }
344                    }
345                }
346            };
347            textView.setTextColor(Color.BLACK);
348            textView.setOnFocusChangeListener(mItemFocusChangeListener);
349            return new ViewHolder(textView);
350        }
351
352        @Override
353        public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) {
354            if (DEBUG) Log.v(TAG, "bindViewHolder " + position + " " + baseHolder);
355            mBoundCount++;
356            ViewHolder holder = (ViewHolder) baseHolder;
357            if (mAlignmentProvider != null) {
358                holder.mItemAlignment = mAlignmentProvider.getItemAlignmentFacet(position);
359            } else {
360                holder.mItemAlignment = null;
361            }
362            if (mChildLayout == -1) {
363                ((TextView) holder.itemView).setText("Item "+mItemLengths[position]);
364                boolean focusable = true;
365                if (mItemFocusables != null) {
366                    focusable = mItemFocusables[position];
367                }
368                ((TextView) holder.itemView).setFocusable(focusable);
369                ((TextView) holder.itemView).setFocusableInTouchMode(focusable);
370                holder.itemView.setBackgroundColor(Color.LTGRAY);
371            } else {
372                if (holder.itemView instanceof TextView) {
373                    ((TextView) holder.itemView).setText("Item "+mItemLengths[position]);
374                }
375            }
376            updateSize(holder.itemView, position);
377        }
378
379        @Override
380        public int getItemCount() {
381            return mNumItems;
382        }
383    }
384
385    void updateSize(View view, int position) {
386        ViewGroup.LayoutParams p = view.getLayoutParams();
387        if (p == null) {
388            p = new ViewGroup.LayoutParams(0, 0);
389        }
390        if (mOrientation == BaseGridView.HORIZONTAL) {
391            p.width = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
392            p.height = mSecondarySizeZero ? 0 : 80;
393        } else {
394            p.width = mSecondarySizeZero ? 0 : 240;
395            p.height = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
396        }
397        view.setLayoutParams(p);
398    }
399
400    static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
401
402        ItemAlignmentFacet mItemAlignment;
403        public ViewHolder(View v) {
404            super(v);
405        }
406
407        @Override
408        public Object getFacet(Class facetClass) {
409            if (facetClass.equals(ItemAlignmentFacet.class)) {
410                return mItemAlignment;
411            }
412            return null;
413        }
414    }
415}
416