Home | History | Annotate | Download | only in widget
      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 android.support.v17.leanback.widget;
     18 
     19 import android.app.Activity;
     20 import android.content.Intent;
     21 import android.graphics.Color;
     22 import android.os.Bundle;
     23 import android.support.v17.leanback.test.R;
     24 import android.support.v7.widget.RecyclerView;
     25 import android.util.Log;
     26 import android.view.View;
     27 import android.view.View.OnFocusChangeListener;
     28 import android.view.ViewGroup;
     29 import android.widget.TextView;
     30 
     31 import java.util.ArrayList;
     32 
     33 public class GridActivity extends Activity {
     34 
     35     private static final String TAG = "GridActivity";
     36 
     37     interface ImportantForAccessibilityListener {
     38         void onImportantForAccessibilityChanged(View view, int newValue);
     39     }
     40 
     41     interface AdapterListener {
     42         void onBind(RecyclerView.ViewHolder vh, int position);
     43     }
     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     public static final String EXTRA_UPDATE_SIZE = "updateSize";
     55     public static final String EXTRA_LAYOUT_MARGINS = "layoutMargins";
     56     public static final String EXTRA_NINEPATCH_SHADOW = "NINEPATCH_SHADOW";
     57     public static final String EXTRA_HAS_STABLE_IDS = "hasStableIds";
     58 
     59     /**
     60      * Class that implements GridWidgetTest.ViewTypeProvider for creating different
     61      * view types for each position.
     62      */
     63     public static final String EXTRA_VIEWTYPEPROVIDER_CLASS = "viewtype_class";
     64     /**
     65      * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
     66      * ItemAlignmentFacet for each ViewHolder.
     67      */
     68     public static final String EXTRA_ITEMALIGNMENTPROVIDER_CLASS = "itemalignment_class";
     69     /**
     70      * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
     71      * ItemAlignmentFacet for a given viewType.
     72      */
     73     public static final String EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS =
     74             "itemalignment_viewtype_class";
     75     public static final String SELECT_ACTION = "android.test.leanback.widget.SELECT";
     76 
     77     static final int DEFAULT_NUM_ITEMS = 100;
     78     static final boolean DEFAULT_STAGGERED = true;
     79     static final boolean DEFAULT_REQUEST_LAYOUT_ONFOCUS = false;
     80     static final boolean DEFAULT_REQUEST_FOCUS_ONLAYOUT = false;
     81 
     82     private static final boolean DEBUG = false;
     83 
     84     int mLayoutId;
     85     int mOrientation;
     86     int mNumItems;
     87     int mChildLayout;
     88     boolean mStaggered;
     89     boolean mRequestLayoutOnFocus;
     90     boolean mRequestFocusOnLayout;
     91     boolean mSecondarySizeZero;
     92     GridWidgetTest.ViewTypeProvider mViewTypeProvider;
     93     GridWidgetTest.ItemAlignmentFacetProvider mAlignmentProvider;
     94     GridWidgetTest.ItemAlignmentFacetProvider mAlignmentViewTypeProvider;
     95     AdapterListener mAdapterListener;
     96     boolean mUpdateSize = true;
     97     boolean mHasStableIds;
     98 
     99     int[] mGridViewLayoutSize;
    100     BaseGridView mGridView;
    101     int[] mItemLengths;
    102     boolean[] mItemFocusables;
    103     int[] mLayoutMargins;
    104     int mNinePatchShadow;
    105 
    106     private int mBoundCount;
    107     ImportantForAccessibilityListener mImportantForAccessibilityListener;
    108 
    109     private View createView() {
    110 
    111         View view = getLayoutInflater().inflate(mLayoutId, null, false);
    112         mGridView = (BaseGridView) view.findViewById(R.id.gridview);
    113         mOrientation = mGridView instanceof HorizontalGridView ? BaseGridView.HORIZONTAL :
    114                 BaseGridView.VERTICAL;
    115         mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
    116         mGridView.setWindowAlignmentOffsetPercent(35);
    117         mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
    118             @Override
    119             public void onChildSelected(ViewGroup parent, View view, int position, long id) {
    120                 if (DEBUG) Log.d(TAG, "onChildSelected position=" + position +  " id="+id);
    121             }
    122         });
    123         if (mNinePatchShadow != 0) {
    124             mGridView.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
    125         }
    126         return view;
    127     }
    128 
    129     @Override
    130     protected void onCreate(Bundle savedInstanceState) {
    131         Intent intent = getIntent();
    132 
    133         mLayoutId = intent.getIntExtra(EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
    134         mChildLayout = intent.getIntExtra(EXTRA_CHILD_LAYOUT_ID, -1);
    135         mStaggered = intent.getBooleanExtra(EXTRA_STAGGERED, DEFAULT_STAGGERED);
    136         mRequestLayoutOnFocus = intent.getBooleanExtra(EXTRA_REQUEST_LAYOUT_ONFOCUS,
    137                 DEFAULT_REQUEST_LAYOUT_ONFOCUS);
    138         mRequestFocusOnLayout = intent.getBooleanExtra(EXTRA_REQUEST_FOCUS_ONLAYOUT,
    139                 DEFAULT_REQUEST_FOCUS_ONLAYOUT);
    140         mUpdateSize = intent.getBooleanExtra(EXTRA_UPDATE_SIZE, true);
    141         mSecondarySizeZero = intent.getBooleanExtra(EXTRA_SECONDARY_SIZE_ZERO, false);
    142         mItemLengths = intent.getIntArrayExtra(EXTRA_ITEMS);
    143         mHasStableIds = intent.getBooleanExtra(EXTRA_HAS_STABLE_IDS, false);
    144         mItemFocusables = intent.getBooleanArrayExtra(EXTRA_ITEMS_FOCUSABLE);
    145         mLayoutMargins = intent.getIntArrayExtra(EXTRA_LAYOUT_MARGINS);
    146         String alignmentClass = intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_CLASS);
    147         String alignmentViewTypeClass =
    148                 intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS);
    149         String viewTypeClass = intent.getStringExtra(EXTRA_VIEWTYPEPROVIDER_CLASS);
    150         mNinePatchShadow = intent.getIntExtra(EXTRA_NINEPATCH_SHADOW, 0);
    151         try {
    152             if (alignmentClass != null) {
    153                 mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
    154                         Class.forName(alignmentClass).newInstance();
    155             }
    156             if (alignmentViewTypeClass != null) {
    157                 mAlignmentViewTypeProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
    158                         Class.forName(alignmentViewTypeClass).newInstance();
    159             }
    160             if (viewTypeClass != null) {
    161                 mViewTypeProvider = (GridWidgetTest.ViewTypeProvider)
    162                         Class.forName(viewTypeClass).newInstance();
    163             }
    164         } catch (ClassNotFoundException ex) {
    165             throw new RuntimeException(ex);
    166         } catch (InstantiationException ex) {
    167             throw new RuntimeException(ex);
    168         } catch (IllegalAccessException ex) {
    169             throw new RuntimeException(ex);
    170         }
    171 
    172         super.onCreate(savedInstanceState);
    173 
    174         if (DEBUG) Log.v(TAG, "onCreate " + this);
    175 
    176         RecyclerView.Adapter adapter = new MyAdapter();
    177         adapter.setHasStableIds(mHasStableIds);
    178 
    179         View view = createView();
    180         if (mItemLengths == null) {
    181             mNumItems = intent.getIntExtra(EXTRA_NUM_ITEMS, DEFAULT_NUM_ITEMS);
    182             mItemLengths = new int[mNumItems];
    183             for (int i = 0; i < mItemLengths.length; i++) {
    184                 if (mOrientation == BaseGridView.HORIZONTAL) {
    185                     mItemLengths[i] = mStaggered ? (int)(Math.random() * 180) + 180 : 240;
    186                 } else {
    187                     mItemLengths[i] = mStaggered ? (int)(Math.random() * 120) + 120 : 160;
    188                 }
    189             }
    190         } else {
    191             mNumItems = mItemLengths.length;
    192         }
    193 
    194         mGridView.setAdapter(adapter);
    195         setContentView(view);
    196     }
    197 
    198     void rebindToNewAdapter() {
    199         mGridView.setAdapter(new MyAdapter());
    200     }
    201 
    202     @Override
    203     protected void onNewIntent(Intent intent) {
    204         if (DEBUG) Log.v(TAG, "onNewIntent " + intent+ " "+this);
    205         if (intent.getAction().equals(SELECT_ACTION)) {
    206             int position = intent.getIntExtra("SELECT_POSITION", -1);
    207             if (position >= 0) {
    208                 mGridView.setSelectedPosition(position);
    209             }
    210         }
    211         super.onNewIntent(intent);
    212     }
    213 
    214     private OnFocusChangeListener mItemFocusChangeListener = new OnFocusChangeListener() {
    215 
    216         @Override
    217         public void onFocusChange(View v, boolean hasFocus) {
    218             if (hasFocus) {
    219                 v.setBackgroundColor(Color.YELLOW);
    220             } else {
    221                 v.setBackgroundColor(Color.LTGRAY);
    222             }
    223             if (mRequestLayoutOnFocus) {
    224                 RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
    225                 int position = vh.getAdapterPosition();
    226                 updateSize(v, position);
    227             }
    228         }
    229     };
    230 
    231     private OnFocusChangeListener mSubItemFocusChangeListener = new OnFocusChangeListener() {
    232 
    233         @Override
    234         public void onFocusChange(View v, boolean hasFocus) {
    235             if (hasFocus) {
    236                 v.setBackgroundColor(Color.YELLOW);
    237             } else {
    238                 v.setBackgroundColor(Color.LTGRAY);
    239             }
    240         }
    241     };
    242 
    243     void resetBoundCount() {
    244         mBoundCount = 0;
    245     }
    246 
    247     int getBoundCount() {
    248        return mBoundCount;
    249     }
    250 
    251     void swap(int index1, int index2) {
    252         if (index1 == index2) {
    253             return;
    254         } else if (index1 > index2) {
    255             int index = index1;
    256             index1 = index2;
    257             index2 = index;
    258         }
    259         int value = mItemLengths[index1];
    260         mItemLengths[index1] = mItemLengths[index2];
    261         mItemLengths[index2] = value;
    262         mGridView.getAdapter().notifyItemMoved(index1, index2);
    263         mGridView.getAdapter().notifyItemMoved(index2 - 1, index1);
    264     }
    265 
    266     void moveItem(int index1, int index2, boolean notify) {
    267         if (index1 == index2) {
    268             return;
    269         }
    270         int[] items = removeItems(index1, 1, false);
    271         addItems(index2, items, false);
    272         if (notify) {
    273             mGridView.getAdapter().notifyItemMoved(index1, index2);
    274         }
    275     }
    276 
    277     void changeArraySize(int length) {
    278         mNumItems = length;
    279         mGridView.getAdapter().notifyDataSetChanged();
    280     }
    281 
    282     int[] removeItems(int index, int length) {
    283         return removeItems(index, length, true);
    284     }
    285 
    286     int[] removeItems(int index, int length, boolean notify) {
    287         int[] removed = new int[length];
    288         System.arraycopy(mItemLengths, index, removed, 0, length);
    289         System.arraycopy(mItemLengths, index + length, mItemLengths, index,
    290                 mNumItems - index - length);
    291         mNumItems -= length;
    292         if (mGridView.getAdapter() != null && notify) {
    293             mGridView.getAdapter().notifyItemRangeRemoved(index, length);
    294         }
    295         return removed;
    296     }
    297 
    298     void attachToNewAdapter(int[] items) {
    299         mItemLengths = items;
    300         mNumItems = items.length;
    301         mGridView.setAdapter(new MyAdapter());
    302     }
    303 
    304 
    305     void changeItem(int position, int itemValue) {
    306         mItemLengths[position] = itemValue;
    307         if (mGridView.getAdapter() != null) {
    308             mGridView.getAdapter().notifyItemChanged(position);
    309         }
    310     }
    311 
    312     void addItems(int index, int[] items) {
    313         addItems(index, items, true);
    314     }
    315 
    316     void addItems(int index, int[] items, boolean notify) {
    317         int length = items.length;
    318         if (mItemLengths.length < mNumItems + length) {
    319             int[] array = new int[mNumItems + length];
    320             System.arraycopy(mItemLengths, 0, array, 0, mNumItems);
    321             mItemLengths = array;
    322         }
    323         System.arraycopy(mItemLengths, index, mItemLengths, index + length, mNumItems - index);
    324         System.arraycopy(items, 0, mItemLengths, index, length);
    325         mNumItems += length;
    326         if (notify && mGridView.getAdapter() != null) {
    327             mGridView.getAdapter().notifyItemRangeInserted(index, length);
    328         }
    329     }
    330 
    331     class MyAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
    332 
    333         @Override
    334         public int getItemViewType(int position) {
    335             if (mViewTypeProvider != null) {
    336                 return mViewTypeProvider.getViewType(position);
    337             }
    338             return 0;
    339         }
    340 
    341         @Override
    342         public FacetProvider getFacetProvider(int viewType) {
    343             final Object alignmentFacet = mAlignmentViewTypeProvider != null
    344                     ? mAlignmentViewTypeProvider.getItemAlignmentFacet(viewType) : null;
    345             if (alignmentFacet != null) {
    346                 return new FacetProvider() {
    347                     @Override
    348                     public Object getFacet(Class facetClass) {
    349                         if (facetClass.equals(ItemAlignmentFacet.class)) {
    350                             return alignmentFacet;
    351                         }
    352                         return null;
    353                     }
    354                 };
    355             }
    356             return null;
    357         }
    358 
    359         @Override
    360         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    361             if (DEBUG) Log.v(TAG, "createViewHolder " + viewType);
    362             View itemView;
    363             if (mChildLayout != -1) {
    364                 final View view = getLayoutInflater().inflate(mChildLayout, parent, false);
    365                 ArrayList<View> focusables = new ArrayList<View>();
    366                 view.addFocusables(focusables, View.FOCUS_UP);
    367                 for (int i = 0; i < focusables.size(); i++) {
    368                     View f = focusables.get(i);
    369                     f.setBackgroundColor(Color.LTGRAY);
    370                     f.setOnFocusChangeListener(new OnFocusChangeListener() {
    371                         @Override
    372                         public void onFocusChange(View v, boolean hasFocus) {
    373                             if (hasFocus) {
    374                                 v.setBackgroundColor(Color.YELLOW);
    375                             } else {
    376                                 v.setBackgroundColor(Color.LTGRAY);
    377                             }
    378                             if (mRequestLayoutOnFocus) {
    379                                 if (v == view) {
    380                                     RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
    381                                     int position = vh.getAdapterPosition();
    382                                     updateSize(v, position);
    383                                 }
    384                                 view.requestLayout();
    385                             }
    386                         }
    387                     });
    388                 }
    389                 itemView = view;
    390             } else {
    391                 TextView textView = new TextView(parent.getContext()) {
    392                     @Override
    393                     protected void onLayout(boolean change, int left, int top, int right,
    394                             int bottom) {
    395                         super.onLayout(change, left, top, right, bottom);
    396                         if (mRequestFocusOnLayout) {
    397                             if (hasFocus()) {
    398                                 clearFocus();
    399                                 requestFocus();
    400                             }
    401                         }
    402                     }
    403 
    404                     @Override
    405                     public void setImportantForAccessibility(int mode) {
    406                         super.setImportantForAccessibility(mode);
    407                         if (mImportantForAccessibilityListener != null) {
    408                             mImportantForAccessibilityListener.onImportantForAccessibilityChanged(
    409                                     this, mode);
    410                         }
    411                     }
    412                 };
    413                 textView.setTextColor(Color.BLACK);
    414                 textView.setOnFocusChangeListener(mItemFocusChangeListener);
    415                 itemView = textView;
    416             }
    417             if (mLayoutMargins != null) {
    418                 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
    419                         itemView.getLayoutParams();
    420                 if (lp == null) {
    421                     lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
    422                             ViewGroup.LayoutParams.WRAP_CONTENT);
    423                 }
    424                 lp.leftMargin = mLayoutMargins[0];
    425                 lp.topMargin = mLayoutMargins[1];
    426                 lp.rightMargin = mLayoutMargins[2];
    427                 lp.bottomMargin = mLayoutMargins[3];
    428                 itemView.setLayoutParams(lp);
    429             }
    430             if (mNinePatchShadow != 0) {
    431                 ViewGroup viewGroup = (ViewGroup) itemView;
    432                 View shadow = new View(viewGroup.getContext());
    433                 shadow.setBackgroundResource(mNinePatchShadow);
    434                 viewGroup.addView(shadow);
    435                 viewGroup.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
    436             }
    437             return new ViewHolder(itemView);
    438         }
    439 
    440         @Override
    441         public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) {
    442             if (DEBUG) Log.v(TAG, "bindViewHolder " + position + " " + baseHolder);
    443             mBoundCount++;
    444             ViewHolder holder = (ViewHolder) baseHolder;
    445             if (mAlignmentProvider != null) {
    446                 holder.mItemAlignment = mAlignmentProvider.getItemAlignmentFacet(position);
    447             } else {
    448                 holder.mItemAlignment = null;
    449             }
    450             if (mChildLayout == -1) {
    451                 ((TextView) holder.itemView).setText("Item "+mItemLengths[position]
    452                         + " type=" + getItemViewType(position));
    453                 boolean focusable = true;
    454                 if (mItemFocusables != null) {
    455                     focusable = mItemFocusables[position];
    456                 }
    457                 ((TextView) holder.itemView).setFocusable(focusable);
    458                 ((TextView) holder.itemView).setFocusableInTouchMode(focusable);
    459                 holder.itemView.setBackgroundColor(Color.LTGRAY);
    460             } else {
    461                 if (holder.itemView instanceof TextView) {
    462                     ((TextView) holder.itemView).setText("Item "+mItemLengths[position]
    463                             + " type=" + getItemViewType(position));
    464                 }
    465             }
    466             updateSize(holder.itemView, position);
    467             if (mAdapterListener != null) {
    468                 mAdapterListener.onBind(baseHolder, position);
    469             }
    470         }
    471 
    472         @Override
    473         public int getItemCount() {
    474             return mNumItems;
    475         }
    476 
    477         @Override
    478         public long getItemId(int position) {
    479             if (!mHasStableIds) return -1;
    480             return position;
    481         }
    482     }
    483 
    484     void updateSize(View view, int position) {
    485         if (!mUpdateSize) {
    486             return;
    487         }
    488         ViewGroup.LayoutParams p = view.getLayoutParams();
    489         if (p == null) {
    490             p = new ViewGroup.LayoutParams(0, 0);
    491         }
    492         if (mOrientation == BaseGridView.HORIZONTAL) {
    493             p.width = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
    494             p.height = mSecondarySizeZero ? 0 : 80;
    495         } else {
    496             p.width = mSecondarySizeZero ? 0 : 240;
    497             p.height = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
    498         }
    499         view.setLayoutParams(p);
    500     }
    501 
    502     static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
    503 
    504         ItemAlignmentFacet mItemAlignment;
    505         public ViewHolder(View v) {
    506             super(v);
    507         }
    508 
    509         @Override
    510         public Object getFacet(Class facetClass) {
    511             if (facetClass.equals(ItemAlignmentFacet.class)) {
    512                 return mItemAlignment;
    513             }
    514             return null;
    515         }
    516     }
    517 }
    518