Home | History | Annotate | Download | only in picker
      1 /*
      2  * Copyright (C) 2014 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.settings.widget.picker;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.app.Fragment;
     24 import android.content.Context;
     25 import android.os.Bundle;
     26 import android.support.annotation.DimenRes;
     27 import android.support.v17.leanback.widget.OnChildSelectedListener;
     28 import android.support.v17.leanback.widget.VerticalGridView;
     29 import android.support.v7.widget.RecyclerView;
     30 import android.util.AttributeSet;
     31 import android.util.TypedValue;
     32 import android.view.KeyEvent;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.view.animation.AccelerateInterpolator;
     37 import android.view.animation.DecelerateInterpolator;
     38 import android.view.animation.Interpolator;
     39 import android.widget.LinearLayout;
     40 import android.widget.TextView;
     41 
     42 import com.android.tv.settings.R;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Arrays;
     46 import java.util.List;
     47 
     48 /**
     49  * Picker class
     50  */
     51 public abstract class Picker extends Fragment {
     52 
     53     /**
     54      * Object listening for adapter events.
     55      */
     56     public interface ResultListener {
     57         void onCommitResult(List<String> result);
     58     }
     59 
     60     private Context mContext;
     61     private List<VerticalGridView> mColumnViews;
     62     private ResultListener mResultListener;
     63     private ArrayList<PickerColumn> mColumns = new ArrayList<>();
     64 
     65     private float mUnfocusedAlpha;
     66     private float mFocusedAlpha;
     67     private float mVisibleColumnAlpha;
     68     private float mInvisibleColumnAlpha;
     69     private int mAlphaAnimDuration;
     70     private Interpolator mDecelerateInterpolator;
     71     private Interpolator mAccelerateInterpolator;
     72     private boolean mKeyDown = false;
     73     private boolean mClicked = false;
     74 
     75     /**
     76      * selection result
     77      */
     78     private List<String> mResult;
     79 
     80     /**
     81      * Classes extending {@link Picker} should override this method to supply
     82      * the columns
     83      */
     84     protected abstract ArrayList<PickerColumn> getColumns();
     85 
     86     /**
     87      * Classes extending {@link Picker} can choose to override this method to
     88      * supply the separator string
     89      */
     90     protected abstract String getSeparator();
     91 
     92     @Override
     93     public void onCreate(Bundle savedInstanceState) {
     94         super.onCreate(savedInstanceState);
     95         mContext = getActivity();
     96 
     97         mFocusedAlpha = getFloat(R.dimen.list_item_selected_title_text_alpha);
     98         mUnfocusedAlpha = getFloat(R.dimen.list_item_unselected_text_alpha);
     99         mVisibleColumnAlpha = getFloat(R.dimen.picker_item_visible_column_item_alpha);
    100         mInvisibleColumnAlpha = getFloat(R.dimen.picker_item_invisible_column_item_alpha);
    101 
    102         mAlphaAnimDuration = mContext.getResources().getInteger(
    103                 R.integer.dialog_animation_duration);
    104 
    105         mDecelerateInterpolator = new DecelerateInterpolator(2.5F);
    106         mAccelerateInterpolator = new AccelerateInterpolator(2.5F);
    107     }
    108 
    109     @Override
    110     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    111             Bundle savedInstanceState) {
    112 
    113         mColumns = getColumns();
    114         if (mColumns == null || mColumns.size() == 0) {
    115             return null;
    116         }
    117 
    118         final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.picker, container, false);
    119         final PickerLayout pickerView = (PickerLayout) rootView.findViewById(R.id.picker);
    120         pickerView.setChildFocusListener(this);
    121         mColumnViews = new ArrayList<>();
    122         mResult = new ArrayList<>();
    123 
    124         int totalCol = mColumns.size();
    125         for (int i = 0; i < totalCol; i++) {
    126             final String[] col = mColumns.get(i).getItems();
    127             mResult.add(col[0]);
    128             final VerticalGridView columnView = (VerticalGridView) inflater.inflate(
    129                     R.layout.picker_column, pickerView, false);
    130             columnView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    131             mColumnViews.add(columnView);
    132             columnView.setTag(i);
    133 
    134             // add view to root
    135             pickerView.addView(columnView);
    136 
    137             // add a separator if not the last element
    138             if (i != totalCol - 1 && getSeparator() != null) {
    139                 final TextView separator =
    140                         (TextView) inflater.inflate(R.layout.picker_separator, pickerView, false);
    141                 separator.setText(getSeparator());
    142                 pickerView.addView(separator);
    143             }
    144         }
    145         initAdapters();
    146         mColumnViews.get(0).requestFocus();
    147 
    148         mClicked = false;
    149         mKeyDown = false;
    150 
    151         return rootView;
    152     }
    153 
    154     private void initAdapters() {
    155         final int totalCol = mColumns.size();
    156         for (int i = 0; i < totalCol; i++) {
    157             VerticalGridView gridView = mColumnViews.get(i);
    158             gridView.setAdapter(new Adapter(i, Arrays.asList(mColumns.get(i).getItems())));
    159             gridView.setOnKeyInterceptListener(new VerticalGridView.OnKeyInterceptListener() {
    160                 @Override
    161                 public boolean onInterceptKeyEvent(KeyEvent event) {
    162                     switch (event.getKeyCode()) {
    163                         case KeyEvent.KEYCODE_DPAD_CENTER:
    164                         case KeyEvent.KEYCODE_ENTER:
    165                             if (event.getAction() == KeyEvent.ACTION_DOWN) {
    166                                 // We are only interested in the Key DOWN event here,
    167                                 // because the Key UP event will generate a click, and
    168                                 // will be handled by OnItemClickListener.
    169                                 if (!mKeyDown) {
    170                                     mKeyDown = true;
    171                                     updateAllColumnsForClick(false);
    172                                 }
    173                             }
    174                             break;
    175                     }
    176                     return false;
    177                 }
    178             });
    179         }
    180     }
    181 
    182     protected void updateAdapter(int index, PickerColumn pickerColumn) {
    183         final VerticalGridView gridView = mColumnViews.get(index);
    184         final Adapter adapter = (Adapter) gridView.getAdapter();
    185 
    186         mColumns.set(index, pickerColumn);
    187         adapter.setItems(Arrays.asList(pickerColumn.getItems()));
    188 
    189         gridView.post(new Runnable() {
    190             @Override
    191             public void run() {
    192                 updateColumn(gridView, false, null);
    193             }
    194         });
    195     }
    196 
    197     protected void updateSelection(int columnIndex, int selectedIndex) {
    198         VerticalGridView columnView = mColumnViews.get(columnIndex);
    199         if (columnView != null) {
    200             columnView.setSelectedPosition(selectedIndex);
    201             String text = mColumns.get(columnIndex).getItems()[selectedIndex];
    202             mResult.set(columnIndex, text);
    203         }
    204     }
    205 
    206     public void setResultListener(ResultListener listener) {
    207         mResultListener = listener;
    208     }
    209 
    210     private void updateAllColumnsForClick(boolean keyUp) {
    211         final ArrayList<Animator> animList = new ArrayList<>();
    212 
    213         for (final VerticalGridView column : mColumnViews) {
    214             final int selected = column.getSelectedPosition();
    215 
    216             final RecyclerView.LayoutManager manager = column.getLayoutManager();
    217             final int size = manager.getChildCount();
    218 
    219             for (int i = 0; i < size; i++) {
    220                 final View item = manager.getChildAt(i);
    221                 if (item != null) {
    222                     if (selected == i) {
    223                         // set alpha for main item (selected) in the column
    224                         if (keyUp) {
    225                             setOrAnimateAlphaInternal(item, true, mFocusedAlpha, mUnfocusedAlpha,
    226                                     animList, mAccelerateInterpolator);
    227                         } else {
    228                             setOrAnimateAlphaInternal(item, true, mUnfocusedAlpha, -1, animList,
    229                                     mDecelerateInterpolator);
    230                         }
    231                     } else if (!keyUp) {
    232                         // hide all non selected items on key down
    233                         setOrAnimateAlphaInternal(item, true, mInvisibleColumnAlpha, -1, animList,
    234                                 mDecelerateInterpolator);
    235                     }
    236                 }
    237             }
    238         }
    239 
    240         if (!animList.isEmpty()) {
    241             AnimatorSet animSet = new AnimatorSet();
    242             animSet.playTogether(animList);
    243 
    244             if (mClicked) {
    245                 animSet.addListener(new AnimatorListenerAdapter() {
    246                     @Override
    247                     public void onAnimationEnd(Animator animation) {
    248                         if (mResultListener != null) {
    249                             mResultListener.onCommitResult(mResult);
    250                         }
    251                     }
    252                 });
    253             }
    254             animSet.start();
    255         } else {
    256             if (mClicked && mResultListener != null) {
    257                 mResultListener.onCommitResult(mResult);
    258             }
    259         }
    260     }
    261 
    262     public void childFocusChanged() {
    263         final ArrayList<Animator> animList = new ArrayList<>();
    264 
    265         for (final VerticalGridView column : mColumnViews) {
    266             updateColumn(column, column.hasFocus(), animList);
    267         }
    268 
    269         if (!animList.isEmpty()) {
    270             AnimatorSet animSet = new AnimatorSet();
    271             animSet.playTogether(animList);
    272             animSet.start();
    273         }
    274     }
    275 
    276     private void updateColumn(VerticalGridView column, boolean animateAlpha,
    277             ArrayList<Animator> animList) {
    278         if (column == null) {
    279             return;
    280         }
    281 
    282         final int selected = column.getSelectedPosition();
    283         final boolean focused = column.hasFocus();
    284 
    285         ArrayList<Animator> localAnimList = animList;
    286         if (animateAlpha && localAnimList == null) {
    287             // no global animation list, create a local one for the current set
    288             localAnimList = new ArrayList<>();
    289         }
    290 
    291         // Iterate through the visible views
    292         final RecyclerView.LayoutManager manager = column.getLayoutManager();
    293         final int size = manager.getChildCount();
    294 
    295         for (int i = 0; i < size; i++) {
    296             final View item = manager.getChildAt(i);
    297             if (item != null) {
    298                 setOrAnimateAlpha(item, (selected == column.getChildAdapterPosition(item)), focused,
    299                         animateAlpha, localAnimList);
    300             }
    301         }
    302         if (animateAlpha && animList == null && !localAnimList.isEmpty()) {
    303             // No global animation list, so play these start the current set of animations now
    304             AnimatorSet animSet = new AnimatorSet();
    305             animSet.playTogether(localAnimList);
    306             animSet.start();
    307         }
    308     }
    309 
    310     private void setOrAnimateAlpha(View view, boolean selected, boolean focused, boolean animate,
    311             ArrayList<Animator> animList) {
    312         if (selected) {
    313             // set alpha for main item (selected) in the column
    314             if ((focused && !mKeyDown) || mClicked) {
    315                 setOrAnimateAlphaInternal(view, animate, mFocusedAlpha, -1, animList,
    316                         mDecelerateInterpolator);
    317             } else {
    318                 setOrAnimateAlphaInternal(view, animate, mUnfocusedAlpha, -1, animList,
    319                         mDecelerateInterpolator);
    320             }
    321         } else {
    322             // set alpha for remaining items in the column
    323             if (focused && !mClicked && !mKeyDown) {
    324                 setOrAnimateAlphaInternal(view, animate, mVisibleColumnAlpha, -1, animList,
    325                         mDecelerateInterpolator);
    326             } else {
    327                 setOrAnimateAlphaInternal(view, animate, mInvisibleColumnAlpha, -1, animList,
    328                         mDecelerateInterpolator);
    329             }
    330         }
    331     }
    332 
    333     private void setOrAnimateAlphaInternal(View view, boolean animate, float destAlpha,
    334             float startAlpha, ArrayList<Animator> animList, Interpolator interpolator) {
    335         view.clearAnimation();
    336         if (!animate) {
    337             view.setAlpha(destAlpha);
    338         } else {
    339             ObjectAnimator anim;
    340             if (startAlpha >= 0.0f) {
    341                 // set a start alpha
    342                 anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, destAlpha);
    343             } else {
    344                 // no start alpha
    345                 anim = ObjectAnimator.ofFloat(view, "alpha", destAlpha);
    346             }
    347             anim.setDuration(mAlphaAnimDuration);
    348             anim.setInterpolator(interpolator);
    349             if (animList != null) {
    350                 animList.add(anim);
    351             } else {
    352                 anim.start();
    353             }
    354         }
    355     }
    356 
    357     /**
    358      * Classes extending {@link Picker} can override this function to supply the
    359      * behavior when a list has been scrolled
    360      */
    361     protected void onScroll(int column, View v, int position) {}
    362 
    363     private float getFloat(@DimenRes int resourceId) {
    364         TypedValue buffer = new TypedValue();
    365         mContext.getResources().getValue(resourceId, buffer, true);
    366         return buffer.getFloat();
    367     }
    368 
    369     private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    370         private final TextView mTextView;
    371 
    372         public ViewHolder(View itemView) {
    373             super(itemView);
    374             mTextView = (TextView) itemView.findViewById(R.id.list_item);
    375             itemView.setOnClickListener(this);
    376         }
    377 
    378         public TextView getTextView() {
    379             return mTextView;
    380         }
    381 
    382         @Override
    383         public void onClick(View v) {
    384             if (mKeyDown) {
    385                 mKeyDown = false;
    386                 mClicked = true;
    387                 updateAllColumnsForClick(true);
    388             }
    389         }
    390     }
    391 
    392     private class Adapter extends RecyclerView.Adapter<ViewHolder>
    393             implements OnChildSelectedListener {
    394 
    395         private final int mColumnId;
    396 
    397         private List<String> mItems;
    398         private VerticalGridView mGridView;
    399 
    400         public Adapter(int columnId, List<String> items) {
    401             mColumnId = columnId;
    402             mItems = items;
    403         }
    404 
    405         @Override
    406         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    407             final View view = getLayoutInflater(null).inflate(R.layout.picker_item, parent, false);
    408             return new ViewHolder(view);
    409         }
    410 
    411         @Override
    412         public void onBindViewHolder(ViewHolder holder, int position) {
    413             final TextView textView = holder.getTextView();
    414             textView.setText(mItems.get(position));
    415             setOrAnimateAlpha(textView, mGridView.getSelectedPosition() == position,
    416                     mGridView.hasFocus(), false, null);
    417         }
    418 
    419         @Override
    420         public int getItemCount() {
    421             return mItems.size();
    422         }
    423 
    424         @Override
    425         public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    426             mGridView = (VerticalGridView) recyclerView;
    427             mGridView.setOnChildSelectedListener(this);
    428         }
    429 
    430         @Override
    431         public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
    432             mGridView = null;
    433         }
    434 
    435         @Override
    436         public void onChildSelected(ViewGroup parent, View view, int position, long id) {
    437             if (mGridView == null) {
    438                 return;
    439             }
    440             final ViewHolder vh = (ViewHolder) mGridView.getChildViewHolder(view);
    441             final TextView textView = vh.getTextView();
    442 
    443             updateColumn(mGridView, mGridView.hasFocus(), null);
    444             mResult.set(mColumnId, textView.getText().toString());
    445             onScroll(mColumnId, textView, position);
    446         }
    447 
    448         public void setItems(List<String> items) {
    449             final List<String> oldItems = mItems;
    450             mItems = items;
    451             notifyDataSetChanged();
    452         }
    453     }
    454 
    455     public static class PickerLayout extends LinearLayout {
    456 
    457         private Picker mChildFocusListener;
    458 
    459         public PickerLayout(Context context) {
    460             super(context);
    461         }
    462 
    463         public PickerLayout(Context context, AttributeSet attrs) {
    464             super(context, attrs);
    465         }
    466 
    467         public PickerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    468             super(context, attrs, defStyleAttr);
    469         }
    470 
    471         public PickerLayout(Context context, AttributeSet attrs, int defStyleAttr,
    472                 int defStyleRes) {
    473             super(context, attrs, defStyleAttr, defStyleRes);
    474         }
    475 
    476         @Override
    477         public void requestChildFocus(View child, View focused) {
    478             super.requestChildFocus(child, focused);
    479 
    480             mChildFocusListener.childFocusChanged();
    481         }
    482 
    483         public void setChildFocusListener(Picker childFocusListener) {
    484             mChildFocusListener = childFocusListener;
    485         }
    486     }
    487 }
    488