Home | History | Annotate | Download | only in picker
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package androidx.leanback.widget.picker;
     16 
     17 import android.content.Context;
     18 import android.graphics.Rect;
     19 import android.text.TextUtils;
     20 import android.util.AttributeSet;
     21 import android.util.TypedValue;
     22 import android.view.KeyEvent;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.view.animation.AccelerateInterpolator;
     27 import android.view.animation.DecelerateInterpolator;
     28 import android.view.animation.Interpolator;
     29 import android.widget.FrameLayout;
     30 import android.widget.TextView;
     31 
     32 import androidx.leanback.R;
     33 import androidx.leanback.widget.OnChildViewHolderSelectedListener;
     34 import androidx.leanback.widget.VerticalGridView;
     35 import androidx.recyclerview.widget.RecyclerView;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.List;
     40 
     41 /**
     42  * Picker is a widget showing multiple customized {@link PickerColumn}s. The PickerColumns are
     43  * initialized in {@link #setColumns(List)}. Call {@link #setColumnAt(int, PickerColumn)} if the
     44  * column value range or labels change. Call {@link #setColumnValue(int, int, boolean)} to update
     45  * the current value of PickerColumn.
     46  * <p>
     47  * Picker has two states and will change height:
     48  * <li>{@link #isActivated()} is true: Picker shows typically three items vertically (see
     49  * {@link #getActivatedVisibleItemCount()}}. Columns other than {@link #getSelectedColumn()} still
     50  * shows one item if the Picker is focused. On a touch screen device, the Picker will not get focus
     51  * so it always show three items on all columns. On a non-touch device (a TV), the Picker will show
     52  * three items only on currently activated column. If the Picker has focus, it will intercept DPAD
     53  * directions and select activated column.
     54  * <li>{@link #isActivated()} is false: Picker shows one item vertically (see
     55  * {@link #getVisibleItemCount()}) on all columns. The size of Picker shrinks.
     56  */
     57 public class Picker extends FrameLayout {
     58 
     59     public interface PickerValueListener {
     60         public void onValueChanged(Picker picker, int column);
     61     }
     62 
     63     private ViewGroup mRootView;
     64     private ViewGroup mPickerView;
     65     final List<VerticalGridView> mColumnViews = new ArrayList<VerticalGridView>();
     66     ArrayList<PickerColumn> mColumns;
     67 
     68     private float mUnfocusedAlpha;
     69     private float mFocusedAlpha;
     70     private float mVisibleColumnAlpha;
     71     private float mInvisibleColumnAlpha;
     72     private int mAlphaAnimDuration;
     73     private Interpolator mDecelerateInterpolator;
     74     private Interpolator mAccelerateInterpolator;
     75     private ArrayList<PickerValueListener> mListeners;
     76     private float mVisibleItemsActivated = 3;
     77     private float mVisibleItems = 1;
     78     private int mSelectedColumn = 0;
     79 
     80     private List<CharSequence> mSeparators = new ArrayList<>();
     81     private int mPickerItemLayoutId = R.layout.lb_picker_item;
     82     private int mPickerItemTextViewId = 0;
     83 
     84     /**
     85      * Gets separator string between columns.
     86      *
     87      * @return The separator that will be populated between all the Picker columns.
     88      * @deprecated Use {@link #getSeparators()}
     89      */
     90     public final CharSequence getSeparator() {
     91         return mSeparators.get(0);
     92     }
     93 
     94     /**
     95      * Sets separator String between Picker columns.
     96      *
     97      * @param separator Separator String between Picker columns.
     98      */
     99     public final void setSeparator(CharSequence separator) {
    100         setSeparators(Arrays.asList(separator));
    101     }
    102 
    103     /**
    104      * Returns the list of separators that will be populated between the picker column fields.
    105      *
    106      * @return The list of separators populated between the picker column fields.
    107      */
    108     public final List<CharSequence> getSeparators() {
    109         return mSeparators;
    110     }
    111 
    112     /**
    113      * Sets the list of separators that will be populated between the Picker columns. The
    114      * number of the separators should be either 1 indicating the same separator used between all
    115      * the columns fields (and nothing will be placed before the first and after the last column),
    116      * or must be one unit larger than the number of columns passed to {@link #setColumns(List)}.
    117      * In the latter case, the list of separators corresponds to the positions before the first
    118      * column all the way to the position after the last column.
    119      * An empty string for a given position indicates no separators needs to be placed for that
    120      * position, otherwise a TextView with the given String will be created and placed there.
    121      *
    122      * @param separators The list of separators to be populated between the Picker columns.
    123      */
    124     public final void setSeparators(List<CharSequence> separators) {
    125         mSeparators.clear();
    126         mSeparators.addAll(separators);
    127     }
    128 
    129     /**
    130      * Classes extending {@link Picker} can choose to override this method to
    131      * supply the {@link Picker}'s item's layout id
    132      */
    133     public final int getPickerItemLayoutId() {
    134         return mPickerItemLayoutId;
    135     }
    136 
    137     /**
    138      * Returns the {@link Picker}'s item's {@link TextView}'s id from within the
    139      * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
    140      * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
    141      * TextView}.
    142      */
    143     public final int getPickerItemTextViewId() {
    144         return mPickerItemTextViewId;
    145     }
    146 
    147     /**
    148      * Sets the {@link Picker}'s item's {@link TextView}'s id from within the
    149      * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
    150      * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
    151      * TextView}.
    152      *
    153      * @param textViewId View id of TextView inside a Picker item, or 0 if the Picker item is a
    154      *                   TextView.
    155      */
    156     public final void setPickerItemTextViewId(int textViewId) {
    157         mPickerItemTextViewId = textViewId;
    158     }
    159 
    160     /**
    161      * Creates a Picker widget.
    162      */
    163     public Picker(Context context, AttributeSet attrs, int defStyleAttr) {
    164         super(context, attrs, defStyleAttr);
    165         // Make it enabled and clickable to receive Click event.
    166         setEnabled(true);
    167         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    168 
    169         mFocusedAlpha = 1f; //getFloat(R.dimen.list_item_selected_title_text_alpha);
    170         mUnfocusedAlpha = 1f; //getFloat(R.dimen.list_item_unselected_text_alpha);
    171         mVisibleColumnAlpha = 0.5f; //getFloat(R.dimen.picker_item_visible_column_item_alpha);
    172         mInvisibleColumnAlpha = 0f; //getFloat(R.dimen.picker_item_invisible_column_item_alpha);
    173 
    174         mAlphaAnimDuration =
    175                 200; // mContext.getResources().getInteger(R.integer.dialog_animation_duration);
    176 
    177         mDecelerateInterpolator = new DecelerateInterpolator(2.5F);
    178         mAccelerateInterpolator = new AccelerateInterpolator(2.5F);
    179 
    180         LayoutInflater inflater = LayoutInflater.from(getContext());
    181         mRootView = (ViewGroup) inflater.inflate(R.layout.lb_picker, this, true);
    182         mPickerView = (ViewGroup) mRootView.findViewById(R.id.picker);
    183     }
    184 
    185     /**
    186      * Get nth PickerColumn.
    187      *
    188      * @param colIndex Index of PickerColumn.
    189      * @return PickerColumn at colIndex or null if {@link #setColumns(List)} is not called yet.
    190      */
    191     public PickerColumn getColumnAt(int colIndex) {
    192         if (mColumns == null) {
    193             return null;
    194         }
    195         return mColumns.get(colIndex);
    196     }
    197 
    198     /**
    199      * Get number of PickerColumns.
    200      *
    201      * @return Number of PickerColumns or 0 if {@link #setColumns(List)} is not called yet.
    202      */
    203     public int getColumnsCount() {
    204         if (mColumns == null) {
    205             return 0;
    206         }
    207         return mColumns.size();
    208     }
    209 
    210     /**
    211      * Set columns and create Views.
    212      *
    213      * @param columns The actual focusable columns of a picker which are scrollable if the field
    214      *                takes more than one value (e.g. for a DatePicker, day, month, and year fields
    215      *                and for TimePicker, hour, minute, and am/pm fields form the columns).
    216      */
    217     public void setColumns(List<PickerColumn> columns) {
    218         if (mSeparators.size() == 0) {
    219             throw new IllegalStateException("Separators size is: " + mSeparators.size()
    220                     + ". At least one separator must be provided");
    221         } else if (mSeparators.size() == 1) {
    222             CharSequence separator = mSeparators.get(0);
    223             mSeparators.clear();
    224             mSeparators.add("");
    225             for (int i = 0; i < columns.size() - 1; i++) {
    226                 mSeparators.add(separator);
    227             }
    228             mSeparators.add("");
    229         } else {
    230             if (mSeparators.size() != (columns.size() + 1)) {
    231                 throw new IllegalStateException("Separators size: " + mSeparators.size() + " must"
    232                         + "equal the size of columns: " + columns.size() + " + 1");
    233             }
    234         }
    235 
    236         mColumnViews.clear();
    237         mPickerView.removeAllViews();
    238         mColumns = new ArrayList<PickerColumn>(columns);
    239         if (mSelectedColumn > mColumns.size() - 1) {
    240             mSelectedColumn = mColumns.size() - 1;
    241         }
    242         LayoutInflater inflater = LayoutInflater.from(getContext());
    243         int totalCol = getColumnsCount();
    244 
    245         if (!TextUtils.isEmpty(mSeparators.get(0))) {
    246             TextView separator = (TextView) inflater.inflate(
    247                     R.layout.lb_picker_separator, mPickerView, false);
    248             separator.setText(mSeparators.get(0));
    249             mPickerView.addView(separator);
    250         }
    251         for (int i = 0; i < totalCol; i++) {
    252             final int colIndex = i;
    253             final VerticalGridView columnView = (VerticalGridView) inflater.inflate(
    254                     R.layout.lb_picker_column, mPickerView, false);
    255             // we don't want VerticalGridView to receive focus.
    256             updateColumnSize(columnView);
    257             // always center aligned, not aligning selected item on top/bottom edge.
    258             columnView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    259             // Width is dynamic, so has fixed size is false.
    260             columnView.setHasFixedSize(false);
    261             columnView.setFocusable(isActivated());
    262             // Setting cache size to zero in order to rebind item views when picker widget becomes
    263             // activated. Rebinding is necessary to update the alphas when the columns are expanded
    264             // as a result of the picker getting activated, otherwise the cached views with the
    265             // wrong alphas could be laid out.
    266             columnView.setItemViewCacheSize(0);
    267 
    268             mColumnViews.add(columnView);
    269             // add view to root
    270             mPickerView.addView(columnView);
    271 
    272             if (!TextUtils.isEmpty(mSeparators.get(i + 1))) {
    273                 // add a separator if not the last element
    274                 TextView separator = (TextView) inflater.inflate(
    275                         R.layout.lb_picker_separator, mPickerView, false);
    276                 separator.setText(mSeparators.get(i + 1));
    277                 mPickerView.addView(separator);
    278             }
    279 
    280             columnView.setAdapter(new PickerScrollArrayAdapter(getContext(),
    281                     getPickerItemLayoutId(), getPickerItemTextViewId(), colIndex));
    282             columnView.setOnChildViewHolderSelectedListener(mColumnChangeListener);
    283         }
    284     }
    285 
    286     /**
    287      * When column labels change or column range changes, call this function to re-populate the
    288      * selection list.  Note this function cannot be called from RecyclerView layout/scroll pass.
    289      *
    290      * @param columnIndex Index of column to update.
    291      * @param column      New column to update.
    292      */
    293     public void setColumnAt(int columnIndex, PickerColumn column) {
    294         mColumns.set(columnIndex, column);
    295         VerticalGridView columnView = mColumnViews.get(columnIndex);
    296         PickerScrollArrayAdapter adapter = (PickerScrollArrayAdapter) columnView.getAdapter();
    297         if (adapter != null) {
    298             adapter.notifyDataSetChanged();
    299         }
    300         columnView.setSelectedPosition(column.getCurrentValue() - column.getMinValue());
    301     }
    302 
    303     /**
    304      * Manually set current value of a column.  The function will update UI and notify listeners.
    305      *
    306      * @param columnIndex  Index of column to update.
    307      * @param value        New value of the column.
    308      * @param runAnimation True to scroll to the value or false otherwise.
    309      */
    310     public void setColumnValue(int columnIndex, int value, boolean runAnimation) {
    311         PickerColumn column = mColumns.get(columnIndex);
    312         if (column.getCurrentValue() != value) {
    313             column.setCurrentValue(value);
    314             notifyValueChanged(columnIndex);
    315             VerticalGridView columnView = mColumnViews.get(columnIndex);
    316             if (columnView != null) {
    317                 int position = value - mColumns.get(columnIndex).getMinValue();
    318                 if (runAnimation) {
    319                     columnView.setSelectedPositionSmooth(position);
    320                 } else {
    321                     columnView.setSelectedPosition(position);
    322                 }
    323             }
    324         }
    325     }
    326 
    327     private void notifyValueChanged(int columnIndex) {
    328         if (mListeners != null) {
    329             for (int i = mListeners.size() - 1; i >= 0; i--) {
    330                 mListeners.get(i).onValueChanged(this, columnIndex);
    331             }
    332         }
    333     }
    334 
    335     /**
    336      * Register a callback to be invoked when the picker's value has changed.
    337      *
    338      * @param listener The callback to ad
    339      */
    340     public void addOnValueChangedListener(PickerValueListener listener) {
    341         if (mListeners == null) {
    342             mListeners = new ArrayList<Picker.PickerValueListener>();
    343         }
    344         mListeners.add(listener);
    345     }
    346 
    347     /**
    348      * Remove a previously installed value changed callback
    349      *
    350      * @param listener The callback to remove.
    351      */
    352     public void removeOnValueChangedListener(PickerValueListener listener) {
    353         if (mListeners != null) {
    354             mListeners.remove(listener);
    355         }
    356     }
    357 
    358     void updateColumnAlpha(int colIndex, boolean animate) {
    359         VerticalGridView column = mColumnViews.get(colIndex);
    360 
    361         int selected = column.getSelectedPosition();
    362         View item;
    363 
    364         for (int i = 0; i < column.getAdapter().getItemCount(); i++) {
    365             item = column.getLayoutManager().findViewByPosition(i);
    366             if (item != null) {
    367                 setOrAnimateAlpha(item, (selected == i), colIndex, animate);
    368             }
    369         }
    370     }
    371 
    372     void setOrAnimateAlpha(View view, boolean selected, int colIndex,
    373             boolean animate) {
    374         boolean columnShownAsActivated = colIndex == mSelectedColumn || !hasFocus();
    375         if (selected) {
    376             // set alpha for main item (selected) in the column
    377             if (columnShownAsActivated) {
    378                 setOrAnimateAlpha(view, animate, mFocusedAlpha, -1, mDecelerateInterpolator);
    379             } else {
    380                 setOrAnimateAlpha(view, animate, mUnfocusedAlpha, -1, mDecelerateInterpolator);
    381             }
    382         } else {
    383             // set alpha for remaining items in the column
    384             if (columnShownAsActivated) {
    385                 setOrAnimateAlpha(view, animate, mVisibleColumnAlpha, -1, mDecelerateInterpolator);
    386             } else {
    387                 setOrAnimateAlpha(view, animate, mInvisibleColumnAlpha, -1,
    388                         mDecelerateInterpolator);
    389             }
    390         }
    391     }
    392 
    393     private void setOrAnimateAlpha(View view, boolean animate, float destAlpha, float startAlpha,
    394             Interpolator interpolator) {
    395         view.animate().cancel();
    396         if (!animate) {
    397             view.setAlpha(destAlpha);
    398         } else {
    399             if (startAlpha >= 0.0f) {
    400                 // set a start alpha
    401                 view.setAlpha(startAlpha);
    402             }
    403             view.animate().alpha(destAlpha)
    404                     .setDuration(mAlphaAnimDuration).setInterpolator(interpolator)
    405                     .start();
    406         }
    407     }
    408 
    409     /**
    410      * Classes extending {@link Picker} can override this function to supply the
    411      * behavior when a list has been scrolled.  Subclass may call {@link #setColumnValue(int, int,
    412      * boolean)} and or {@link #setColumnAt(int, PickerColumn)}.  Subclass should not directly call
    413      * {@link PickerColumn#setCurrentValue(int)} which does not update internal state or notify
    414      * listeners.
    415      *
    416      * @param columnIndex index of which column was changed.
    417      * @param newValue    A new value desired to be set on the column.
    418      */
    419     public void onColumnValueChanged(int columnIndex, int newValue) {
    420         PickerColumn column = mColumns.get(columnIndex);
    421         if (column.getCurrentValue() != newValue) {
    422             column.setCurrentValue(newValue);
    423             notifyValueChanged(columnIndex);
    424         }
    425     }
    426 
    427     private float getFloat(int resourceId) {
    428         TypedValue buffer = new TypedValue();
    429         getContext().getResources().getValue(resourceId, buffer, true);
    430         return buffer.getFloat();
    431     }
    432 
    433     static class ViewHolder extends RecyclerView.ViewHolder {
    434         final TextView textView;
    435 
    436         ViewHolder(View v, TextView textView) {
    437             super(v);
    438             this.textView = textView;
    439         }
    440     }
    441 
    442     class PickerScrollArrayAdapter extends RecyclerView.Adapter<ViewHolder> {
    443 
    444         private final int mResource;
    445         private final int mColIndex;
    446         private final int mTextViewResourceId;
    447         private PickerColumn mData;
    448 
    449         PickerScrollArrayAdapter(Context context, int resource, int textViewResourceId,
    450                 int colIndex) {
    451             mResource = resource;
    452             mColIndex = colIndex;
    453             mTextViewResourceId = textViewResourceId;
    454             mData = mColumns.get(mColIndex);
    455         }
    456 
    457         @Override
    458         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    459             LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    460             View v = inflater.inflate(mResource, parent, false);
    461             TextView textView;
    462             if (mTextViewResourceId != 0) {
    463                 textView = (TextView) v.findViewById(mTextViewResourceId);
    464             } else {
    465                 textView = (TextView) v;
    466             }
    467             ViewHolder vh = new ViewHolder(v, textView);
    468             return vh;
    469         }
    470 
    471         @Override
    472         public void onBindViewHolder(ViewHolder holder, int position) {
    473             if (holder.textView != null && mData != null) {
    474                 holder.textView.setText(mData.getLabelFor(mData.getMinValue() + position));
    475             }
    476             setOrAnimateAlpha(holder.itemView,
    477                     (mColumnViews.get(mColIndex).getSelectedPosition() == position),
    478                     mColIndex, false);
    479         }
    480 
    481         @Override
    482         public void onViewAttachedToWindow(ViewHolder holder) {
    483             holder.itemView.setFocusable(isActivated());
    484         }
    485 
    486         @Override
    487         public int getItemCount() {
    488             return mData == null ? 0 : mData.getCount();
    489         }
    490     }
    491 
    492     private final OnChildViewHolderSelectedListener mColumnChangeListener = new
    493             OnChildViewHolderSelectedListener() {
    494 
    495                 @Override
    496                 public void onChildViewHolderSelected(RecyclerView parent,
    497                         RecyclerView.ViewHolder child,
    498                         int position, int subposition) {
    499                     PickerScrollArrayAdapter pickerScrollArrayAdapter =
    500                             (PickerScrollArrayAdapter) parent
    501                                     .getAdapter();
    502 
    503                     int colIndex = mColumnViews.indexOf(parent);
    504                     updateColumnAlpha(colIndex, true);
    505                     if (child != null) {
    506                         int newValue = mColumns.get(colIndex).getMinValue() + position;
    507                         onColumnValueChanged(colIndex, newValue);
    508                     }
    509                 }
    510 
    511             };
    512 
    513     @Override
    514     public boolean dispatchKeyEvent(android.view.KeyEvent event) {
    515         if (isActivated()) {
    516             final int keyCode = event.getKeyCode();
    517             switch (keyCode) {
    518                 case KeyEvent.KEYCODE_DPAD_CENTER:
    519                 case KeyEvent.KEYCODE_ENTER:
    520                     if (event.getAction() == KeyEvent.ACTION_UP) {
    521                         performClick();
    522                     }
    523                     break;
    524                 default:
    525                     return super.dispatchKeyEvent(event);
    526             }
    527             return true;
    528         }
    529         return super.dispatchKeyEvent(event);
    530     }
    531 
    532     @Override
    533     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    534         int column = getSelectedColumn();
    535         if (column < mColumnViews.size()) {
    536             return mColumnViews.get(column).requestFocus(direction, previouslyFocusedRect);
    537         }
    538         return false;
    539     }
    540 
    541     /**
    542      * Classes extending {@link Picker} can choose to override this method to
    543      * supply the {@link Picker}'s column's single item height in pixels.
    544      */
    545     protected int getPickerItemHeightPixels() {
    546         return getContext().getResources().getDimensionPixelSize(R.dimen.picker_item_height);
    547     }
    548 
    549     private void updateColumnSize() {
    550         for (int i = 0; i < getColumnsCount(); i++) {
    551             updateColumnSize(mColumnViews.get(i));
    552         }
    553     }
    554 
    555     private void updateColumnSize(VerticalGridView columnView) {
    556         ViewGroup.LayoutParams lp = columnView.getLayoutParams();
    557         float itemCount = isActivated() ? getActivatedVisibleItemCount() : getVisibleItemCount();
    558         lp.height = (int) (getPickerItemHeightPixels() * itemCount
    559                 + columnView.getVerticalSpacing() * (itemCount - 1));
    560         columnView.setLayoutParams(lp);
    561     }
    562 
    563     private void updateItemFocusable() {
    564         final boolean activated = isActivated();
    565         for (int i = 0; i < getColumnsCount(); i++) {
    566             VerticalGridView grid = mColumnViews.get(i);
    567             for (int j = 0; j < grid.getChildCount(); j++) {
    568                 View view = grid.getChildAt(j);
    569                 view.setFocusable(activated);
    570             }
    571         }
    572     }
    573 
    574     /**
    575      * Returns number of visible items showing in a column when it's activated.  The default value
    576      * is 3.
    577      *
    578      * @return Number of visible items showing in a column when it's activated.
    579      */
    580     public float getActivatedVisibleItemCount() {
    581         return mVisibleItemsActivated;
    582     }
    583 
    584     /**
    585      * Changes number of visible items showing in a column when it's activated.  The default value
    586      * is 3.
    587      *
    588      * @param visiblePickerItems Number of visible items showing in a column when it's activated.
    589      */
    590     public void setActivatedVisibleItemCount(float visiblePickerItems) {
    591         if (visiblePickerItems <= 0) {
    592             throw new IllegalArgumentException();
    593         }
    594         if (mVisibleItemsActivated != visiblePickerItems) {
    595             mVisibleItemsActivated = visiblePickerItems;
    596             if (isActivated()) {
    597                 updateColumnSize();
    598             }
    599         }
    600     }
    601 
    602     /**
    603      * Returns number of visible items showing in a column when it's not activated.  The default
    604      * value is 1.
    605      *
    606      * @return Number of visible items showing in a column when it's not activated.
    607      */
    608     public float getVisibleItemCount() {
    609         return 1;
    610     }
    611 
    612     /**
    613      * Changes number of visible items showing in a column when it's not activated.  The default
    614      * value is 1.
    615      *
    616      * @param pickerItems Number of visible items showing in a column when it's not activated.
    617      */
    618     public void setVisibleItemCount(float pickerItems) {
    619         if (pickerItems <= 0) {
    620             throw new IllegalArgumentException();
    621         }
    622         if (mVisibleItems != pickerItems) {
    623             mVisibleItems = pickerItems;
    624             if (!isActivated()) {
    625                 updateColumnSize();
    626             }
    627         }
    628     }
    629 
    630     @Override
    631     public void setActivated(boolean activated) {
    632         if (activated == isActivated()) {
    633             super.setActivated(activated);
    634             return;
    635         }
    636         super.setActivated(activated);
    637         boolean hadFocus = hasFocus();
    638         int column = getSelectedColumn();
    639         // To avoid temporary focus loss in both the following cases, we set Picker's flag to
    640         // FOCUS_BEFORE_DESCENDANTS first, and then back to FOCUS_AFTER_DESCENDANTS once done with
    641         // the focus logic.
    642         // 1. When changing from activated to deactivated, the Picker should grab the focus
    643         // back if it's focusable. However, calling requestFocus on it will transfer the focus down
    644         // to its children if it's flag is FOCUS_AFTER_DESCENDANTS.
    645         // 2. When changing from deactivated to activated, while setting focusable flags on each
    646         // column VerticalGridView, that column will call requestFocus (regardless of which column
    647         // is the selected column) since the currently focused view (Picker) has a flag of
    648         // FOCUS_AFTER_DESCENDANTS.
    649         setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
    650         if (!activated && hadFocus && isFocusable()) {
    651             // When picker widget that originally had focus is deactivated and it is focusable, we
    652             // should not pass the focus down to the children. The Picker itself will capture focus.
    653             requestFocus();
    654         }
    655 
    656         for (int i = 0; i < getColumnsCount(); i++) {
    657             mColumnViews.get(i).setFocusable(activated);
    658         }
    659 
    660         updateColumnSize();
    661         updateItemFocusable();
    662         if (activated && hadFocus && (column >= 0)) {
    663             mColumnViews.get(column).requestFocus();
    664         }
    665         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    666     }
    667 
    668     @Override
    669     public void requestChildFocus(View child, View focused) {
    670         super.requestChildFocus(child, focused);
    671         for (int i = 0; i < mColumnViews.size(); i++) {
    672             if (mColumnViews.get(i).hasFocus()) {
    673                 setSelectedColumn(i);
    674             }
    675         }
    676     }
    677 
    678     /**
    679      * Change current selected column.  Picker shows multiple items on selected column if Picker has
    680      * focus.  Picker shows multiple items on all column if Picker has no focus (e.g. a Touchscreen
    681      * screen).
    682      *
    683      * @param columnIndex Index of column to activate.
    684      */
    685     public void setSelectedColumn(int columnIndex) {
    686         if (mSelectedColumn != columnIndex) {
    687             mSelectedColumn = columnIndex;
    688             for (int i = 0; i < mColumnViews.size(); i++) {
    689                 updateColumnAlpha(i, true);
    690             }
    691         }
    692     }
    693 
    694     /**
    695      * Get current activated column index.
    696      *
    697      * @return Current activated column index.
    698      */
    699     public int getSelectedColumn() {
    700         return mSelectedColumn;
    701     }
    702 
    703 }
    704