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 {
onValueChanged(Picker picker, int column)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      */
getSeparator()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      */
setSeparator(CharSequence separator)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      */
getSeparators()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      */
setSeparators(List<CharSequence> separators)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      */
getPickerItemLayoutId()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      */
getPickerItemTextViewId()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      */
setPickerItemTextViewId(int textViewId)156     public final void setPickerItemTextViewId(int textViewId) {
157         mPickerItemTextViewId = textViewId;
158     }
159 
160     /**
161      * Creates a Picker widget.
162      */
Picker(Context context, AttributeSet attrs, int defStyleAttr)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      */
getColumnAt(int colIndex)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      */
getColumnsCount()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      */
setColumns(List<PickerColumn> columns)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      */
setColumnAt(int columnIndex, PickerColumn column)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      */
setColumnValue(int columnIndex, int value, boolean runAnimation)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 
notifyValueChanged(int columnIndex)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      */
addOnValueChangedListener(PickerValueListener listener)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      */
removeOnValueChangedListener(PickerValueListener listener)352     public void removeOnValueChangedListener(PickerValueListener listener) {
353         if (mListeners != null) {
354             mListeners.remove(listener);
355         }
356     }
357 
updateColumnAlpha(int colIndex, boolean animate)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 
setOrAnimateAlpha(View view, boolean selected, int colIndex, boolean animate)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 
setOrAnimateAlpha(View view, boolean animate, float destAlpha, float startAlpha, Interpolator interpolator)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      */
onColumnValueChanged(int columnIndex, int newValue)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 
getFloat(int resourceId)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 
ViewHolder(View v, TextView textView)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 
PickerScrollArrayAdapter(Context context, int resource, int textViewResourceId, int colIndex)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
onCreateViewHolder(ViewGroup parent, int viewType)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
onBindViewHolder(ViewHolder holder, int position)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
onViewAttachedToWindow(ViewHolder holder)482         public void onViewAttachedToWindow(ViewHolder holder) {
483             holder.itemView.setFocusable(isActivated());
484         }
485 
486         @Override
getItemCount()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
dispatchKeyEvent(android.view.KeyEvent event)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
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)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      */
getPickerItemHeightPixels()545     protected int getPickerItemHeightPixels() {
546         return getContext().getResources().getDimensionPixelSize(R.dimen.picker_item_height);
547     }
548 
updateColumnSize()549     private void updateColumnSize() {
550         for (int i = 0; i < getColumnsCount(); i++) {
551             updateColumnSize(mColumnViews.get(i));
552         }
553     }
554 
updateColumnSize(VerticalGridView columnView)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 
updateItemFocusable()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      */
getActivatedVisibleItemCount()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      */
setActivatedVisibleItemCount(float visiblePickerItems)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      */
getVisibleItemCount()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      */
setVisibleItemCount(float pickerItems)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
setActivated(boolean activated)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
requestChildFocus(View child, View focused)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      */
setSelectedColumn(int columnIndex)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      */
getSelectedColumn()699     public int getSelectedColumn() {
700         return mSelectedColumn;
701     }
702 
703 }
704