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 17package com.android.tv.settings.dialog; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.database.DataSetObserver; 26import android.graphics.Bitmap; 27import android.graphics.drawable.Drawable; 28import android.media.AudioManager; 29import android.net.Uri; 30import android.os.Handler; 31import android.os.Looper; 32import android.support.v17.leanback.R; 33import android.support.v7.widget.RecyclerView; 34import android.support.v7.widget.RecyclerView.ViewHolder; 35import android.text.TextUtils; 36import android.util.Log; 37import android.util.TypedValue; 38import android.view.KeyEvent; 39import android.view.LayoutInflater; 40import android.view.View; 41import android.view.ViewGroup; 42import android.view.WindowManager; 43import android.view.animation.DecelerateInterpolator; 44import android.view.animation.Interpolator; 45import android.widget.AdapterView.OnItemSelectedListener; 46import android.widget.ImageView; 47import android.widget.TextView; 48 49import com.android.tv.settings.dialog.Layout; 50 51import java.security.InvalidParameterException; 52import java.util.ArrayList; 53 54/** 55 * Adapter class which creates actions. 56 */ 57class SettingsLayoutAdapter extends RecyclerView.Adapter { 58 private static final String TAG = "SettingsLayoutAdapter"; 59 private static final boolean DEBUG = false; 60 private static final DecelerateInterpolator ALPHA_DECEL = new DecelerateInterpolator(2F); 61 private final Handler mRefreshViewHandler = new Handler(Looper.getMainLooper()); 62 63 /** 64 * Object listening for adapter events. 65 */ 66 public interface Listener { 67 68 /** 69 * Called when the user clicks on an action. 70 */ 71 public void onRowClicked(Layout.LayoutRow item); 72 } 73 74 public interface OnFocusListener { 75 76 /** 77 * Called when the user focuses on an action. 78 */ 79 public void onActionFocused(Layout.LayoutRow item); 80 } 81 82 private final ActionOnKeyPressAnimator mActionOnKeyPressAnimator; 83 private final ActionOnFocusAnimator mActionOnFocusAnimator; 84 private LayoutInflater mInflater; 85 private ArrayList<Layout.LayoutRow> mLayoutRows; 86 private Listener mListener; 87 private boolean mNoAnimateMode = false; 88 private boolean mFocusListenerEnabled = true; 89 90 private final View.OnClickListener mOnClickListener = new View.OnClickListener() { 91 @Override 92 public void onClick(View v) { 93 if (v != null && v.getWindowToken() != null && mListener != null) { 94 mListener.onRowClicked(((LayoutRowViewHolder) v.getTag(R.id.action_title)). 95 getLayoutRow()); 96 } 97 } 98 }; 99 100 public SettingsLayoutAdapter(Listener listener, OnFocusListener onFocusListener) { 101 super(); 102 mListener = listener; 103 mActionOnKeyPressAnimator = new ActionOnKeyPressAnimator(listener); 104 mActionOnFocusAnimator = new ActionOnFocusAnimator(onFocusListener); 105 } 106 107 public void setLayoutRows(ArrayList<Layout.LayoutRow> layoutRows) { 108 mLayoutRows = layoutRows; 109 } 110 111 public void setNoAnimateMode() { 112 mNoAnimateMode = true; 113 } 114 115 public void setFocusListenerEnabled(boolean enabled) { 116 mFocusListenerEnabled = enabled; 117 } 118 119 @Override 120 public int getItemViewType(int position) { 121 return mLayoutRows.get(position).getViewType(); 122 } 123 124 @Override 125 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 126 if (mInflater == null) { 127 mInflater = (LayoutInflater) parent.getContext().getSystemService( 128 Context.LAYOUT_INFLATER_SERVICE); 129 } 130 View v = null; 131 switch (viewType) { 132 case Layout.LayoutRow.VIEW_TYPE_ACTION: 133 v = mInflater.inflate(R.layout.lb_dialog_action_list_item, parent, false); 134 break; 135 case Layout.LayoutRow.VIEW_TYPE_STATIC: 136 v = mInflater.inflate(R.layout.lb_dialog_static_list_item, parent, false); 137 break; 138 } 139 v.setTag(R.layout.lb_dialog_action_list_item, parent); 140 LayoutRowViewHolder viewHolder = new LayoutRowViewHolder(v, mActionOnKeyPressAnimator, 141 mActionOnFocusAnimator, mOnClickListener); 142 viewHolder.init(viewType); 143 return viewHolder; 144 } 145 146 @Override 147 public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) { 148 LayoutRowViewHolder holder = (LayoutRowViewHolder) baseHolder; 149 if (position < mLayoutRows.size()) { 150 holder.bind(mLayoutRows.get(position)); 151 } 152 } 153 154 @Override 155 public int getItemCount() { 156 return mLayoutRows.size(); 157 } 158 159 public void setListener(Listener listener) { 160 mListener = listener; 161 mActionOnKeyPressAnimator.setListener(listener); 162 } 163 164 public void setOnFocusListener(OnFocusListener onFocusListener) { 165 mActionOnFocusAnimator.setOnFocusListener(onFocusListener); 166 } 167 168 public void registerDataSetObserver(DataSetObserver dataSetObserver) { 169 } 170 171 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 172 } 173 174 private class LayoutRowViewHolder extends ViewHolder implements 175 Layout.ContentNodeRefreshListener { 176 177 private class RefreshDescription implements Runnable { 178 public String mDescriptionText; 179 180 @Override 181 public void run() { 182 mDescription.setText(mDescriptionText); 183 } 184 } 185 186 private final ActionOnKeyPressAnimator mActionOnKeyPressAnimator; 187 private final ActionOnFocusAnimator mActionOnFocusAnimator; 188 private final View.OnClickListener mViewOnClickListener; 189 private Layout.LayoutRow mLayoutRow; 190 private TextView mDescription = null; 191 private TextView mTitle; 192 private ImageView mCheckmarkView; 193 private ImageView mIndicatorView; 194 private View mContent; 195 private ImageView mChevronView; 196 private int mViewType; 197 private RefreshDescription mRefreshDescription; 198 199 public LayoutRowViewHolder(View v, ActionOnKeyPressAnimator actionOnKeyPressAnimator, 200 ActionOnFocusAnimator actionOnFocusAnimator, 201 View.OnClickListener viewOnClickListener) { 202 super(v); 203 mActionOnKeyPressAnimator = actionOnKeyPressAnimator; 204 mActionOnFocusAnimator = actionOnFocusAnimator; 205 mViewOnClickListener = viewOnClickListener; 206 } 207 208 public Layout.LayoutRow getLayoutRow() { 209 return mLayoutRow; 210 } 211 212 public void init(int viewType) { 213 mViewType = viewType; 214 mTitle = (TextView) itemView.findViewById(R.id.action_title); 215 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) { 216 mDescription = (TextView) itemView.findViewById(R.id.action_description); 217 } 218 mCheckmarkView = (ImageView) itemView.findViewById(R.id.action_checkmark); 219 mIndicatorView = (ImageView) itemView.findViewById(R.id.action_icon); 220 mContent = itemView.findViewById(R.id.action_content); 221 mChevronView = (ImageView) itemView.findViewById(R.id.action_next_chevron); 222 itemView.setTag(R.id.action_title, this); 223 itemView.setOnKeyListener(mActionOnKeyPressAnimator); 224 itemView.setOnClickListener(mViewOnClickListener); 225 itemView.setOnFocusChangeListener(mActionOnFocusAnimator); 226 } 227 228 //TODO need to create separate xxxViewHolder classes to eliminate tests of "mViewType". 229 public void onRefreshView() { 230 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) { 231 Layout.StringGetter description = mLayoutRow.getDescription(); 232 if (description != null) { 233 String text = description.get(); 234 if (!TextUtils.equals(mRefreshDescription.mDescriptionText, text)) { 235 mRefreshDescription.mDescriptionText = text; 236 mRefreshViewHandler.removeCallbacks(mRefreshDescription); 237 mRefreshViewHandler.post(mRefreshDescription); 238 } 239 } 240 } 241 } 242 243 public void bind(Layout.LayoutRow layoutRow) { 244 mLayoutRow = layoutRow; 245 if (mViewType != layoutRow.getViewType()) { 246 throw new InvalidParameterException("view type does not match"); 247 } 248 249 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) { 250 Layout.StringGetter description = layoutRow.getDescription(); 251 if (description != null) { 252 mRefreshDescription = new RefreshDescription(); 253 String text = description.get(); 254 mDescription.setText(text); 255 mRefreshDescription.mDescriptionText = text; 256 mDescription.setVisibility(View.VISIBLE); 257 description.setListener(this); 258 } else { 259 mDescription.setVisibility(View.GONE); 260 } 261 } 262 263 mTitle.setText(layoutRow.getTitle()); 264 mCheckmarkView.setVisibility(layoutRow.isChecked() ? View.VISIBLE : View.INVISIBLE); 265 266 ViewGroup.LayoutParams contentLp = mContent.getLayoutParams(); 267 if (setIndicator(mIndicatorView, layoutRow)) { 268 contentLp.width = itemView.getContext().getResources() 269 .getDimensionPixelSize(R.dimen.lb_action_text_width); 270 } else { 271 contentLp.width = itemView.getContext().getResources() 272 .getDimensionPixelSize(R.dimen.lb_action_text_width_no_icon); 273 } 274 mContent.setLayoutParams(contentLp); 275 276 mChevronView.setVisibility(layoutRow.hasNext() ? View.VISIBLE : View.INVISIBLE); 277 278 final Resources res = itemView.getContext().getResources(); 279 if (layoutRow.hasMultilineDescription()) { 280 mTitle.setMaxLines(res.getInteger(R.integer.lb_dialog_action_title_max_lines)); 281 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) { 282 mDescription.setMaxHeight( 283 getDescriptionMaxHeight(itemView.getContext(), mTitle)); 284 } 285 } else { 286 mTitle.setMaxLines(res.getInteger(R.integer.lb_dialog_action_title_min_lines)); 287 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) { 288 mDescription.setMaxLines( 289 res.getInteger(R.integer.lb_dialog_action_description_min_lines)); 290 } 291 } 292 293 mActionOnFocusAnimator.unFocus(itemView); 294 } 295 296 private boolean setIndicator(final ImageView indicatorView, Layout.LayoutRow action) { 297 298 Context context = indicatorView.getContext(); 299 Drawable indicator = action.getIcon(); 300 if (indicator != null) { 301 indicatorView.setImageDrawable(indicator); 302 indicatorView.setVisibility(View.VISIBLE); 303 } else { 304 Uri iconUri = action.getIconUri(); 305 if (iconUri != null) { 306 indicatorView.setVisibility(View.INVISIBLE); 307 } else { 308 indicatorView.setVisibility(View.GONE); 309 return false; 310 } 311 } 312 return true; 313 } 314 315 private void fadeIn(View v) { 316 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", 0f, 1f); 317 alphaAnimator.setDuration( 318 v.getContext().getResources().getInteger( 319 android.R.integer.config_mediumAnimTime)); 320 alphaAnimator.start(); 321 } 322 323 /** 324 * @return the max height in pixels the description can be such that the 325 * action nicely takes up the entire screen. 326 */ 327 private int getDescriptionMaxHeight(Context context, TextView title) { 328 final Resources res = context.getResources(); 329 final float verticalPadding = 330 res.getDimension(R.dimen.lb_dialog_list_item_vertical_padding); 331 final int titleMaxLines = res.getInteger(R.integer.lb_dialog_action_title_max_lines); 332 final int displayHeight = ((WindowManager) context.getSystemService( 333 Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight(); 334 335 // The 2 multiplier on the title height calculation is a 336 // conservative estimate for font padding which can not be 337 // calculated at this stage since the view hasn't been rendered yet. 338 return (int) (displayHeight - 339 2 * verticalPadding - 2 * titleMaxLines * title.getLineHeight()); 340 } 341 342 } 343 344 private class ActionOnFocusAnimator implements View.OnFocusChangeListener { 345 346 private boolean mResourcesSet; 347 private float mUnselectedAlpha; 348 private float mSelectedTitleAlpha; 349 private float mDisabledTitleAlpha; 350 private float mSelectedDescriptionAlpha; 351 private float mDisabledDescriptionAlpha; 352 private float mUnselectedDescriptionAlpha; 353 private float mSelectedChevronAlpha; 354 private float mDisabledChevronAlpha; 355 private int mAnimationDuration; 356 private OnFocusListener mOnFocusListener; 357 private View mSelectedView; 358 359 ActionOnFocusAnimator(OnFocusListener onFocusListener) { 360 mOnFocusListener = onFocusListener; 361 } 362 363 public void setOnFocusListener(OnFocusListener onFocusListener) { 364 mOnFocusListener = onFocusListener; 365 } 366 367 public void unFocus(View v) { 368 changeFocus((v != null) ? v : mSelectedView, false, false); 369 } 370 371 @Override 372 public void onFocusChange(View v, boolean hasFocus) { 373 if (hasFocus) { 374 mSelectedView = v; 375 if (mNoAnimateMode) { 376 mNoAnimateMode = false; 377 changeFocus(v, true /* hasFocus */, false /* shouldAnimate */); 378 } else { 379 changeFocus(v, true /* hasFocus */, true /* shouldAnimate */); 380 if (mOnFocusListener != null && mFocusListenerEnabled) { 381 // We still call onActionFocused so that listeners can clear state if they 382 // want. 383 mOnFocusListener.onActionFocused( 384 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow()); 385 } 386 } 387 } else { 388 if (mSelectedView == v) { 389 mSelectedView = null; 390 } 391 changeFocus(v, false /* hasFocus */, true /* shouldAnimate */); 392 } 393 } 394 395 private void changeFocus(View v, boolean hasFocus, boolean shouldAnimate) { 396 if (v == null) { 397 return; 398 } 399 400 if (!mResourcesSet) { 401 mResourcesSet = true; 402 final Resources res = v.getContext().getResources(); 403 404 mAnimationDuration = res.getInteger(R.integer.lb_dialog_animation_duration); 405 406 mUnselectedAlpha = 407 getFloat(res, R.string.lb_dialog_list_item_unselected_text_alpha); 408 409 mSelectedTitleAlpha = 410 getFloat(res, R.string.lb_dialog_list_item_selected_title_text_alpha); 411 mDisabledTitleAlpha = 412 getFloat(res, R.string.lb_dialog_list_item_disabled_title_text_alpha); 413 414 mSelectedDescriptionAlpha = 415 getFloat(res, R.string.lb_dialog_list_item_selected_description_text_alpha); 416 mUnselectedDescriptionAlpha = getFloat(res, 417 R.string.lb_dialog_list_item_unselected_description_text_alpha); 418 mDisabledDescriptionAlpha = 419 getFloat(res, R.string.lb_dialog_list_item_disabled_description_text_alpha); 420 421 mSelectedChevronAlpha = getFloat(res, 422 R.string.lb_dialog_list_item_selected_chevron_background_alpha); 423 mDisabledChevronAlpha = getFloat(res, 424 R.string.lb_dialog_list_item_disabled_chevron_background_alpha); 425 } 426 427 Layout.LayoutRow layoutRow = 428 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow(); 429 430 float titleAlpha; 431 if (layoutRow.isEnabled() && !layoutRow.infoOnly()) { 432 titleAlpha = hasFocus ? mSelectedTitleAlpha : mUnselectedAlpha; 433 } else { 434 titleAlpha = mDisabledTitleAlpha; 435 } 436 float descriptionAlpha; 437 if (!hasFocus || layoutRow.infoOnly()) { 438 descriptionAlpha = mUnselectedDescriptionAlpha; 439 } else { 440 descriptionAlpha = layoutRow.isEnabled() ? mSelectedDescriptionAlpha : 441 mDisabledDescriptionAlpha; 442 } 443 float chevronAlpha; 444 if (layoutRow.hasNext() && !layoutRow.infoOnly()) { 445 chevronAlpha = 446 layoutRow.isEnabled() ? mSelectedChevronAlpha : mDisabledChevronAlpha; 447 } else { 448 chevronAlpha = 0; 449 } 450 451 TextView title = (TextView) v.findViewById(R.id.action_title); 452 setAlpha(title, shouldAnimate, titleAlpha); 453 454 TextView description = (TextView) v.findViewById(R.id.action_description); 455 if (description != null) { 456 setAlpha(description, shouldAnimate, descriptionAlpha); 457 } 458 459 ImageView checkmark = (ImageView) v.findViewById(R.id.action_checkmark); 460 setAlpha(checkmark, shouldAnimate, titleAlpha); 461 462 ImageView icon = (ImageView) v.findViewById(R.id.action_icon); 463 setAlpha(icon, shouldAnimate, titleAlpha); 464 465 ImageView chevron = (ImageView) v.findViewById(R.id.action_next_chevron); 466 setAlpha(chevron, shouldAnimate, chevronAlpha); 467 } 468 469 private void setAlpha(View view, boolean shouldAnimate, float alpha) { 470 if (shouldAnimate) { 471 view.animate().alpha(alpha) 472 .setDuration(mAnimationDuration) 473 .setInterpolator(ALPHA_DECEL) 474 .start(); 475 } else { 476 view.setAlpha(alpha); 477 } 478 } 479 } 480 481 private class ActionOnKeyPressAnimator implements View.OnKeyListener { 482 483 private static final int SELECT_ANIM_DURATION = 100; 484 private static final int SELECT_ANIM_DELAY = 0; 485 private static final float SELECT_ANIM_SELECTED_ALPHA = 0.2f; 486 private static final float SELECT_ANIM_UNSELECTED_ALPHA = 1.0f; 487 private static final float CHECKMARK_ANIM_UNSELECTED_ALPHA = 0.0f; 488 private static final float CHECKMARK_ANIM_SELECTED_ALPHA = 1.0f; 489 490 private boolean mKeyPressed = false; 491 private Listener mListener; 492 493 public ActionOnKeyPressAnimator(Listener listener) { 494 mListener = listener; 495 } 496 497 public void setListener(Listener listener) { 498 mListener = listener; 499 } 500 501 /** 502 * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event. 503 */ 504 @Override 505 public boolean onKey(View v, int keyCode, KeyEvent event) { 506 if (v == null) { 507 return false; 508 } 509 boolean handled = false; 510 Layout.LayoutRow layoutRow = 511 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow(); 512 switch (keyCode) { 513 case KeyEvent.KEYCODE_DPAD_CENTER: 514 case KeyEvent.KEYCODE_NUMPAD_ENTER: 515 case KeyEvent.KEYCODE_BUTTON_X: 516 case KeyEvent.KEYCODE_BUTTON_Y: 517 case KeyEvent.KEYCODE_ENTER: 518 519 if (!layoutRow.isEnabled() || layoutRow.infoOnly()) { 520 if (v.isSoundEffectsEnabled() 521 && event.getAction() == KeyEvent.ACTION_DOWN) { 522 playSound(v.getContext(), AudioManager.FX_KEYPRESS_INVALID); 523 } 524 return true; 525 } 526 527 switch (event.getAction()) { 528 case KeyEvent.ACTION_DOWN: 529 if (!mKeyPressed) { 530 mKeyPressed = true; 531 532 if (v.isSoundEffectsEnabled()) { 533 playSound(v.getContext(), AudioManager.FX_KEY_CLICK); 534 } 535 536 if (DEBUG) { 537 Log.d(TAG, "Enter Key down"); 538 } 539 540 prepareAndAnimateView(v, SELECT_ANIM_UNSELECTED_ALPHA, 541 SELECT_ANIM_SELECTED_ALPHA, SELECT_ANIM_DURATION, 542 SELECT_ANIM_DELAY, null, mKeyPressed); 543 handled = true; 544 } 545 break; 546 case KeyEvent.ACTION_UP: 547 if (mKeyPressed) { 548 mKeyPressed = false; 549 550 if (DEBUG) { 551 Log.d(TAG, "Enter Key up"); 552 } 553 554 prepareAndAnimateView(v, SELECT_ANIM_SELECTED_ALPHA, 555 SELECT_ANIM_UNSELECTED_ALPHA, SELECT_ANIM_DURATION, 556 SELECT_ANIM_DELAY, null, mKeyPressed); 557 handled = true; 558 } 559 break; 560 default: 561 break; 562 } 563 break; 564 default: 565 break; 566 } 567 return handled; 568 } 569 570 private void playSound(Context context, int soundEffect) { 571 AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 572 manager.playSoundEffect(soundEffect); 573 } 574 575 private void prepareAndAnimateView(final View v, float initAlpha, float destAlpha, 576 int duration, 577 int delay, Interpolator interpolator, final boolean pressed) { 578 if (v != null && v.getWindowToken() != null) { 579 final Layout.LayoutRow layoutRow = 580 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow(); 581 582 if (!pressed) { 583 fadeCheckmarks(v, layoutRow, duration, delay, interpolator); 584 } 585 586 v.setAlpha(initAlpha); 587 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 588 v.buildLayer(); 589 v.animate().alpha(destAlpha).setDuration(duration).setStartDelay(delay); 590 if (interpolator != null) { 591 v.animate().setInterpolator(interpolator); 592 } 593 v.animate().setListener(new AnimatorListenerAdapter() { 594 @Override 595 public void onAnimationEnd(Animator animation) { 596 v.setLayerType(View.LAYER_TYPE_NONE, null); 597 if (!pressed) { 598 if (mListener != null) { 599 mListener.onRowClicked(layoutRow); 600 } 601 } 602 } 603 }); 604 v.animate().start(); 605 } 606 } 607 608 private void fadeCheckmarks(final View v, final Layout.LayoutRow action, int duration, 609 int delay, Interpolator interpolator) { 610 int actionCheckSetId = action.getCheckSetId(); 611 if (actionCheckSetId != Layout.LayoutRow.NO_CHECK_SET) { 612 ViewGroup parent = (ViewGroup) v.getTag(R.layout.lb_dialog_action_list_item); 613 614 // Find any actions that are checked and are in the same group as the selected 615 // action. Fade their checkmarks out. 616 for (int i = 0, size = mLayoutRows.size(); i < size; i++) { 617 Layout.LayoutRow a = mLayoutRows.get(i); 618 if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) { 619 a.setChecked(false); 620 View viewToAnimateOut = parent.getChildAt(i); 621 if (viewToAnimateOut != null) { 622 final View checkView = viewToAnimateOut.findViewById( 623 R.id.action_checkmark); 624 checkView.animate().alpha(CHECKMARK_ANIM_UNSELECTED_ALPHA) 625 .setDuration(duration).setStartDelay(delay); 626 if (interpolator != null) { 627 checkView.animate().setInterpolator(interpolator); 628 } 629 checkView.animate().setListener(new AnimatorListenerAdapter() { 630 @Override 631 public void onAnimationEnd(Animator animation) { 632 checkView.setVisibility(View.INVISIBLE); 633 } 634 }); 635 } 636 } 637 } 638 639 // If we we'ren't already checked, fade our checkmark in. 640 if (!action.isChecked()) { 641 action.setChecked(true); 642 final View checkView = v.findViewById(R.id.action_checkmark); 643 checkView.setVisibility(View.VISIBLE); 644 checkView.setAlpha(CHECKMARK_ANIM_UNSELECTED_ALPHA); 645 checkView.animate().alpha(CHECKMARK_ANIM_SELECTED_ALPHA).setDuration(duration) 646 .setStartDelay(delay); 647 if (interpolator != null) { 648 checkView.animate().setInterpolator(interpolator); 649 } 650 checkView.animate().setListener(null); 651 } 652 } 653 } 654 } 655 656 private static float getFloat(Resources res, int floatResId) { 657 TypedValue tv = new TypedValue(); 658 res.getValue(floatResId, tv, true); 659 return tv.getFloat(); 660 } 661} 662