Home | History | Annotate | Download | only in dialog
      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.dialog;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.res.Resources;
     25 import android.database.DataSetObserver;
     26 import android.graphics.Bitmap;
     27 import android.graphics.drawable.Drawable;
     28 import android.media.AudioManager;
     29 import android.net.Uri;
     30 import android.os.Handler;
     31 import android.os.Looper;
     32 import android.support.v17.leanback.R;
     33 import android.support.v7.widget.RecyclerView;
     34 import android.support.v7.widget.RecyclerView.ViewHolder;
     35 import android.text.TextUtils;
     36 import android.util.Log;
     37 import android.util.TypedValue;
     38 import android.view.KeyEvent;
     39 import android.view.LayoutInflater;
     40 import android.view.View;
     41 import android.view.ViewGroup;
     42 import android.view.WindowManager;
     43 import android.view.animation.DecelerateInterpolator;
     44 import android.view.animation.Interpolator;
     45 import android.widget.AdapterView.OnItemSelectedListener;
     46 import android.widget.ImageView;
     47 import android.widget.TextView;
     48 
     49 import com.android.tv.settings.dialog.Layout;
     50 
     51 import java.security.InvalidParameterException;
     52 import java.util.ArrayList;
     53 
     54 /**
     55  * Adapter class which creates actions.
     56  */
     57 class 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