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