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.util.TypedValue; 27 import android.view.KeyEvent; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewGroup.LayoutParams; 32 import android.view.animation.AccelerateInterpolator; 33 import android.view.animation.DecelerateInterpolator; 34 import android.view.animation.Interpolator; 35 import android.widget.AdapterView; 36 import android.widget.AdapterView.OnItemClickListener; 37 import android.widget.TextView; 38 39 import com.android.tv.settings.widget.ScrollAdapterView; 40 import com.android.tv.settings.widget.ScrollArrayAdapter; 41 import com.android.tv.settings.R; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 47 /** 48 * Picker class 49 */ 50 public class Picker extends Fragment { 51 52 /** 53 * Object listening for adapter events. 54 */ 55 public interface ResultListener { onCommitResult(List<String> result)56 void onCommitResult(List<String> result); 57 } 58 59 private Context mContext; 60 private String mSeparator; 61 private ViewGroup mRootView; 62 private ViewGroup mPickerView; 63 private List<ScrollAdapterView> mColumnViews; 64 private ResultListener mResultListener; 65 private ChangeTextColorOnFocus mColumnChangeListener; 66 private ArrayList<PickerColumn> mColumns = new ArrayList<PickerColumn>(); 67 protected PickerConstant mConstant; 68 69 private float mUnfocusedAlpha; 70 private float mFocusedAlpha; 71 private float mVisibleColumnAlpha; 72 private float mInvisibleColumnAlpha; 73 private int mAlphaAnimDuration; 74 private Interpolator mDecelerateInterpolator; 75 private Interpolator mAccelerateInterpolator; 76 private boolean mKeyDown = false; 77 private boolean mClicked = false; 78 79 /** 80 * selection result 81 */ 82 private List<String> mResult; 83 84 private OnItemClickListener mOnClickListener = new OnItemClickListener() { 85 @Override 86 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 87 if (mKeyDown) { 88 mKeyDown = false; 89 mClicked = true; 90 updateAllColumnsForClick(true); 91 } 92 } 93 }; 94 newInstance()95 public static Picker newInstance() { 96 return new Picker(); 97 } 98 99 /** 100 * Classes extending {@link Picker} should override this method to supply 101 * the columns 102 */ getColumns()103 protected ArrayList<PickerColumn> getColumns() { 104 return null; 105 } 106 107 /** 108 * Classes extending {@link Picker} can choose to override this method to 109 * supply the separator string 110 */ getSeparator()111 protected String getSeparator() { 112 return mSeparator; 113 } 114 115 /** 116 * Classes extending {@link Picker} can choose to override this method to 117 * supply the {@link Picker}'s root layout id 118 */ getRootLayoutId()119 protected int getRootLayoutId() { 120 return R.layout.picker; 121 } 122 123 /** 124 * Classes extending {@link Picker} can choose to override this method to 125 * supply the {@link Picker}'s id from within the layout provided by 126 * {@link Picker#getRootLayoutId()} 127 */ getPickerId()128 protected int getPickerId() { 129 return R.id.picker; 130 } 131 132 /** 133 * Classes extending {@link Picker} can choose to override this method to 134 * supply the {@link Picker}'s separator's layout id 135 */ getPickerSeparatorLayoutId()136 protected int getPickerSeparatorLayoutId() { 137 return R.layout.picker_separator; 138 } 139 140 /** 141 * Classes extending {@link Picker} can choose to override this method to 142 * supply the {@link Picker}'s item's layout id 143 */ getPickerItemLayoutId()144 protected int getPickerItemLayoutId() { 145 return R.layout.picker_item; 146 } 147 148 /** 149 * Classes extending {@link Picker} can choose to override this method to 150 * supply the {@link Picker}'s item's {@link TextView}'s id from within the 151 * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the 152 * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link 153 * TextView}. 154 */ getPickerItemTextViewId()155 protected int getPickerItemTextViewId() { 156 return 0; 157 } 158 159 /** 160 * Classes extending {@link Picker} can choose to override this method to 161 * supply the {@link Picker}'s column's height in pixels. 162 */ getPickerColumnHeightPixels()163 protected int getPickerColumnHeightPixels() { 164 return getActivity().getResources().getDimensionPixelSize(R.dimen.picker_column_height); 165 } 166 167 @Override onCreate(Bundle savedInstanceState)168 public void onCreate(Bundle savedInstanceState) { 169 super.onCreate(savedInstanceState); 170 mContext = getActivity(); 171 mConstant = PickerConstant.getInstance(mContext.getResources()); 172 173 mFocusedAlpha = getFloat(R.dimen.list_item_selected_title_text_alpha); 174 mUnfocusedAlpha = getFloat(R.dimen.list_item_unselected_text_alpha); 175 mVisibleColumnAlpha = getFloat(R.dimen.picker_item_visible_column_item_alpha); 176 mInvisibleColumnAlpha = getFloat(R.dimen.picker_item_invisible_column_item_alpha); 177 178 mColumnChangeListener = new ChangeTextColorOnFocus(); 179 mAlphaAnimDuration = mContext.getResources().getInteger( 180 R.integer.dialog_animation_duration); 181 182 mDecelerateInterpolator = new DecelerateInterpolator(2.5F); 183 mAccelerateInterpolator = new AccelerateInterpolator(2.5F); 184 } 185 186 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)187 public View onCreateView(LayoutInflater inflater, ViewGroup container, 188 Bundle savedInstanceState) { 189 190 mColumns = getColumns(); 191 if (mColumns == null || mColumns.size() == 0) { 192 return null; 193 } 194 195 mRootView = (ViewGroup) inflater.inflate(getRootLayoutId(), null); 196 mPickerView = (ViewGroup) mRootView.findViewById(getPickerId()); 197 mColumnViews = new ArrayList<ScrollAdapterView>(); 198 mResult = new ArrayList<String>(); 199 200 int totalCol = mColumns.size(); 201 for (int i = 0; i < totalCol; i++) { 202 final int colIndex = i; 203 final String[] col = mColumns.get(i).getItems(); 204 mResult.add(col[0]); 205 final ScrollAdapterView columnView = (ScrollAdapterView) inflater.inflate( 206 R.layout.picker_column, mPickerView, false); 207 LayoutParams lp = columnView.getLayoutParams(); 208 lp.height = getPickerColumnHeightPixels(); 209 columnView.setLayoutParams(lp); 210 mColumnViews.add(columnView); 211 columnView.setTag(Integer.valueOf(colIndex)); 212 213 // add view to root 214 mPickerView.addView(columnView); 215 216 // add a separator if not the last element 217 if (i != totalCol - 1 && getSeparator() != null) { 218 TextView separator = (TextView) inflater.inflate( 219 getPickerSeparatorLayoutId(), mPickerView, false); 220 separator.setText(getSeparator()); 221 mPickerView.addView(separator); 222 } 223 } 224 initAdapters(); 225 mColumnViews.get(0).requestFocus(); 226 227 mClicked = false; 228 mKeyDown = false; 229 230 return mRootView; 231 } 232 initAdapters()233 private void initAdapters() { 234 final int totalCol = mColumns.size(); 235 for (int i = 0; i < totalCol; i++) { 236 final int colIndex = i; 237 ScrollAdapterView columnView = mColumnViews.get(i); 238 final String[] col = mColumns.get(i).getItems(); 239 setAdapter(columnView, col, colIndex); 240 columnView.setOnFocusChangeListener(mColumnChangeListener); 241 columnView.setOnItemSelectedListener(mColumnChangeListener); 242 columnView.setOnItemClickListener(mOnClickListener); 243 244 columnView.setOnKeyListener(new View.OnKeyListener() { 245 @Override 246 public boolean onKey(View v, int keyCode, KeyEvent event) { 247 switch (keyCode) { 248 case KeyEvent.KEYCODE_DPAD_CENTER: 249 case KeyEvent.KEYCODE_ENTER: 250 if (event.getAction() == KeyEvent.ACTION_DOWN) { 251 // We are only interested in the Key DOWN event here, 252 // because the Key UP event will generate a click, and 253 // will be handled by OnItemClickListener. 254 if (!mKeyDown) { 255 mKeyDown = true; 256 updateAllColumnsForClick(false); 257 } 258 } 259 break; 260 } 261 return false; 262 } 263 }); 264 } 265 } 266 unregisterListeners()267 private void unregisterListeners() { 268 final int totalCol = mColumns.size(); 269 for (int i = 0; i < totalCol; i++) { 270 ScrollAdapterView columnView = mColumnViews.get(i); 271 columnView.setOnFocusChangeListener(null); 272 columnView.setOnItemSelectedListener(null); 273 columnView.setOnItemClickListener(null); 274 columnView.setOnKeyListener(null); 275 } 276 } 277 setAdapter(ScrollAdapterView columnView, final String[] col, final int colIndex)278 private void setAdapter(ScrollAdapterView columnView, final String[] col, final int colIndex) { 279 List<String> arrayList = new ArrayList<String>(Arrays.asList(col)); 280 PickerScrollArrayAdapter pickerScrollArrayAdapter = (getPickerItemTextViewId() == 0) ? 281 new PickerScrollArrayAdapter(mContext, getPickerItemLayoutId(), arrayList, colIndex) 282 : new PickerScrollArrayAdapter(mContext, getPickerItemLayoutId(), 283 getPickerItemTextViewId(), arrayList, colIndex); 284 columnView.setAdapter(pickerScrollArrayAdapter); 285 } 286 updateAdapter(final int index, PickerColumn pickerColumn)287 protected void updateAdapter(final int index, PickerColumn pickerColumn) { 288 ScrollAdapterView columnView = mColumnViews.get(index); 289 final String[] col = pickerColumn.getItems(); 290 291 ScrollArrayAdapter<String> adapter = (ScrollArrayAdapter<String>)(columnView.getAdapter()); 292 if (adapter != null) { 293 adapter.setNotifyOnChange(false); 294 adapter.clear(); 295 adapter.addAll(col); 296 adapter.notifyDataSetChanged(); 297 } 298 299 updateColumn(columnView, false, null); 300 mColumns.set(index, pickerColumn); 301 } 302 updateSelection(int columnIndex, int selectedIndex)303 protected void updateSelection(int columnIndex, int selectedIndex) { 304 ScrollAdapterView columnView = mColumnViews.get(columnIndex); 305 if (columnView != null) { 306 columnView.setSelection(selectedIndex); 307 String text = mColumns.get(columnIndex).getItems()[selectedIndex]; 308 mResult.set(columnIndex, text); 309 } 310 } 311 setResultListener(ResultListener listener)312 public void setResultListener(ResultListener listener) { 313 mResultListener = listener; 314 } 315 updateAllColumnsForClick(boolean keyUp)316 private void updateAllColumnsForClick(boolean keyUp) { 317 ArrayList<Animator> animList = null; 318 animList = new ArrayList<Animator>(); 319 View item; 320 321 for (int j = 0; j < mColumnViews.size(); j++) { 322 ScrollAdapterView column = mColumnViews.get(j); 323 int selected = column.getSelectedItemPosition(); 324 for (int i = 0; i < column.getAdapter().getCount(); i++) { 325 item = column.getItemView(i); 326 if (item != null) { 327 if (selected == i) { 328 // set alpha for main item (selected) in the column 329 if (keyUp) { 330 setOrAnimateAlpha(item, true, mFocusedAlpha, mUnfocusedAlpha, animList, 331 mAccelerateInterpolator); 332 } else { 333 setOrAnimateAlpha(item, true, mUnfocusedAlpha, -1, animList, 334 mDecelerateInterpolator); 335 } 336 } else if (!keyUp) { 337 // hide all non selected items on key down 338 setOrAnimateAlpha(item, true, mInvisibleColumnAlpha, -1, animList, 339 mDecelerateInterpolator); 340 } 341 } 342 } 343 } 344 345 if (animList != null && animList.size() > 0) { 346 AnimatorSet animSet = new AnimatorSet(); 347 animSet.playTogether(animList); 348 349 if (mClicked) { 350 animSet.addListener(new AnimatorListenerAdapter() { 351 @Override 352 public void onAnimationEnd(Animator animation) { 353 if (mResultListener != null) { 354 mResultListener.onCommitResult(mResult); 355 } 356 } 357 }); 358 } 359 animSet.start(); 360 } 361 } 362 updateColumn(ScrollAdapterView column, boolean animateAlpha, ArrayList<Animator> animList)363 private void updateColumn(ScrollAdapterView column, boolean animateAlpha, 364 ArrayList<Animator> animList) { 365 if (column == null) { 366 return; 367 } 368 369 int selected = column.getSelectedItemPosition(); 370 View item; 371 boolean focused = column.hasFocus(); 372 373 ArrayList<Animator> localAnimList = animList; 374 if (animateAlpha && localAnimList == null) { 375 // no global animation list, create a local one for the current set 376 localAnimList = new ArrayList<Animator>(); 377 } 378 379 for (int i = 0; i < column.getAdapter().getCount(); i++) { 380 item = column.getItemView(i); 381 if (item != null) { 382 setOrAnimateAlpha(item, (selected == i), focused, animateAlpha, localAnimList); 383 } 384 } 385 if (animateAlpha && animList == null && localAnimList != null && localAnimList.size() > 0) { 386 // No global animation list, so play these start the current set of animations now 387 AnimatorSet animSet = new AnimatorSet(); 388 animSet.playTogether(localAnimList); 389 animSet.start(); 390 } 391 } 392 setOrAnimateAlpha(View view, boolean selected, boolean focused, boolean animate, ArrayList<Animator> animList)393 private void setOrAnimateAlpha(View view, boolean selected, boolean focused, boolean animate, 394 ArrayList<Animator> animList) { 395 if (selected) { 396 // set alpha for main item (selected) in the column 397 if ((focused && !mKeyDown) || mClicked) { 398 setOrAnimateAlpha(view, animate, mFocusedAlpha, -1, animList, 399 mDecelerateInterpolator); 400 } else { 401 setOrAnimateAlpha(view, animate, mUnfocusedAlpha, -1, animList, 402 mDecelerateInterpolator); 403 } 404 } else { 405 // set alpha for remaining items in the column 406 if (focused && !mClicked && !mKeyDown) { 407 setOrAnimateAlpha(view, animate, mVisibleColumnAlpha, -1, animList, 408 mDecelerateInterpolator); 409 } else { 410 setOrAnimateAlpha(view, animate, mInvisibleColumnAlpha, -1, animList, 411 mDecelerateInterpolator); 412 } 413 } 414 } 415 setOrAnimateAlpha(View view, boolean animate, float destAlpha, float startAlpha, ArrayList<Animator> animList, Interpolator interpolator)416 private void setOrAnimateAlpha(View view, boolean animate, float destAlpha, float startAlpha, 417 ArrayList<Animator> animList, Interpolator interpolator) { 418 view.clearAnimation(); 419 if (!animate) { 420 view.setAlpha(destAlpha); 421 } else { 422 ObjectAnimator anim; 423 if (startAlpha >= 0.0f) { 424 // set a start alpha 425 anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, destAlpha); 426 } else { 427 // no start alpha 428 anim = ObjectAnimator.ofFloat(view, "alpha", destAlpha); 429 } 430 anim.setDuration(mAlphaAnimDuration); 431 anim.setInterpolator(interpolator); 432 if (animList != null) { 433 animList.add(anim); 434 } else { 435 anim.start(); 436 } 437 } 438 } 439 440 /** 441 * Classes extending {@link Picker} can override this function to supply the 442 * behavior when a list has been scrolled 443 */ onScroll(View v)444 protected void onScroll(View v) { 445 } 446 447 @Override onDestroyView()448 public void onDestroyView() { 449 unregisterListeners(); 450 if (mColumnChangeListener != null) { 451 mColumnChangeListener.setDisabled(); 452 } 453 super.onDestroyView(); 454 } 455 getFloat(int resourceId)456 private float getFloat(int resourceId) { 457 TypedValue buffer = new TypedValue(); 458 mContext.getResources().getValue(resourceId, buffer, true); 459 return buffer.getFloat(); 460 } 461 462 private class PickerScrollArrayAdapter extends ScrollArrayAdapter<String> { 463 464 private final int mColIndex; 465 private final int mTextViewResourceId; 466 PickerScrollArrayAdapter(Context context, int resource, List<String> objects, int colIndex)467 PickerScrollArrayAdapter(Context context, int resource, 468 List<String> objects, int colIndex) { 469 super(context, resource, objects); 470 mColIndex = colIndex; 471 mTextViewResourceId = 0; 472 } 473 PickerScrollArrayAdapter(Context context, int resource, int textViewResourceId, List<String> objects, int colIndex)474 PickerScrollArrayAdapter(Context context, int resource, int textViewResourceId, 475 List<String> objects, int colIndex) { 476 super(context, resource, textViewResourceId, objects); 477 mColIndex = colIndex; 478 mTextViewResourceId = textViewResourceId; 479 } 480 481 @Override getView(int position, View convertView, ViewGroup parent)482 public View getView(int position, View convertView, ViewGroup parent) { 483 View view = super.getView(position, convertView, parent); 484 view.setTag(Integer.valueOf(mColIndex)); 485 setOrAnimateAlpha(view, 486 (mColumnViews.get(mColIndex).getSelectedItemPosition() == position), false, 487 false, null); 488 return view; 489 } 490 getTextViewFromAdapterView(View adapterView)491 TextView getTextViewFromAdapterView(View adapterView) { 492 if (mTextViewResourceId != 0) { 493 return (TextView) adapterView.findViewById(mTextViewResourceId); 494 } else { 495 return (TextView) adapterView; 496 } 497 } 498 } 499 500 501 private class ChangeTextColorOnFocus implements View.OnFocusChangeListener, 502 AdapterView.OnItemSelectedListener { 503 private boolean mDisabled; 504 ChangeTextColorOnFocus()505 ChangeTextColorOnFocus() { 506 mDisabled = false; 507 } 508 setDisabled()509 public void setDisabled() { 510 mDisabled = true; 511 } 512 513 @Override onItemSelected(AdapterView<?> parent, View view, int position, long id)514 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 515 if (mDisabled) { 516 // If the listener has been disabled (because the view is being destroyed) 517 // then just ignore this call. 518 return; 519 } 520 521 PickerScrollArrayAdapter pickerScrollArrayAdapter = (PickerScrollArrayAdapter) parent 522 .getAdapter(); 523 524 TextView textView = pickerScrollArrayAdapter.getTextViewFromAdapterView(view); 525 526 int colIndex = (Integer) parent.getTag(); 527 528 updateColumn((ScrollAdapterView) parent, parent.hasFocus(), null); 529 530 mResult.set(colIndex, textView.getText().toString()); 531 onScroll(textView); 532 } 533 534 @Override onNothingSelected(AdapterView<?> parent)535 public void onNothingSelected(AdapterView<?> parent) { 536 // N/A 537 } 538 539 @Override onFocusChange(View view, boolean hasFocus)540 public void onFocusChange(View view, boolean hasFocus) { 541 if (mDisabled) { 542 // If the listener has been disabled (because the view is being destroyed) 543 // then just ignore this call. 544 return; 545 } 546 547 if (view instanceof ScrollAdapterView) { 548 updateColumn((ScrollAdapterView) view, true, null); 549 } 550 } 551 } 552 } 553