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