SettingsLayoutFragment.java revision 01a5b81394670c6c5c2011673ed48c4fee1cf77b
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.AnimatorInflater;
21import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.animation.TimeInterpolator;
25import android.animation.ValueAnimator;
26import android.app.Activity;
27import android.app.Fragment;
28import android.app.FragmentManager;
29import android.app.FragmentTransaction;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.content.res.Resources;
34import android.graphics.Bitmap;
35import android.graphics.Color;
36import android.graphics.drawable.ColorDrawable;
37import android.graphics.drawable.Drawable;
38import android.net.Uri;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.Parcel;
42import android.os.Parcelable;
43import android.support.v17.leanback.R;
44import android.support.v17.leanback.widget.VerticalGridView;
45import android.support.v7.widget.RecyclerView;
46import android.util.Log;
47import android.view.LayoutInflater;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.ViewPropertyAnimator;
51import android.view.ViewTreeObserver;
52import android.view.ViewGroup.LayoutParams;
53import android.view.animation.DecelerateInterpolator;
54import android.view.animation.Interpolator;
55import android.widget.ImageView;
56import android.widget.RelativeLayout;
57import android.widget.TextView;
58
59import java.util.ArrayList;
60import java.util.List;
61
62import com.android.tv.settings.dialog.Layout;
63import com.android.tv.settings.util.AccessibilityHelper;
64
65/**
66 * Displays content on the left and actions on the right.
67 */
68public class SettingsLayoutFragment extends Fragment implements Layout.LayoutNodeRefreshListener {
69
70    private static final String TAG_LEAN_BACK_DIALOG_FRAGMENT = "leanBackSettingsLayoutFragment";
71    private static final String EXTRA_CONTENT_TITLE = "title";
72    private static final String EXTRA_CONTENT_BREADCRUMB = "breadcrumb";
73    private static final String EXTRA_CONTENT_DESCRIPTION = "description";
74    private static final String EXTRA_CONTENT_ICON = "icon";
75    private static final String EXTRA_CONTENT_ICON_URI = "iconUri";
76    private static final String EXTRA_CONTENT_ICON_BITMAP = "iconBitmap";
77    private static final String EXTRA_CONTENT_ICON_BACKGROUND = "iconBackground";
78    private static final String EXTRA_ACTION_NAME = "name";
79    private static final String EXTRA_ACTION_LAYOUT = "layout";
80    private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
81    private static final String EXTRA_ENTRY_TRANSITION_PERFORMED = "entryTransitionPerformed";
82    private static final int ANIMATION_FRAGMENT_ENTER = 1;
83    private static final int ANIMATION_FRAGMENT_EXIT = 2;
84    private static final int ANIMATION_FRAGMENT_ENTER_POP = 3;
85    private static final int ANIMATION_FRAGMENT_EXIT_POP = 4;
86    private static final float WINDOW_ALIGNMENT_OFFSET_PERCENT = 50f;
87    private static final float FADE_IN_ALPHA_START = 0f;
88    private static final float FADE_IN_ALPHA_FINISH = 1f;
89    private static final float SLIDE_OUT_ANIMATOR_LEFT = 0f;
90    private static final float SLIDE_OUT_ANIMATOR_RIGHT = 200f;
91    private static final float SLIDE_OUT_ANIMATOR_START_ALPHA = 0f;
92    private static final float SLIDE_OUT_ANIMATOR_END_ALPHA = 1f;
93
94    public interface Listener {
95        void onActionClicked(Layout.Action action);
96    };
97
98    /**
99     * Builds a SettingsLayoutFragment object.
100     */
101    public static class Builder {
102
103        private String mContentTitle;
104        private String mContentBreadcrumb;
105        private String mContentDescription;
106        private Drawable mIcon;
107        private Uri mIconUri;
108        private Bitmap mIconBitmap;
109        private int mIconBackgroundColor = Color.TRANSPARENT;
110        private Layout mLayout;
111        private String mName;
112
113        public SettingsLayoutFragment build() {
114            SettingsLayoutFragment fragment = new SettingsLayoutFragment();
115            Bundle args = new Bundle();
116            args.putString(EXTRA_CONTENT_TITLE, mContentTitle);
117            args.putString(EXTRA_CONTENT_BREADCRUMB, mContentBreadcrumb);
118            args.putString(EXTRA_CONTENT_DESCRIPTION, mContentDescription);
119            //args.putParcelable(EXTRA_CONTENT_ICON, mIcon);
120            fragment.mIcon = mIcon;
121            args.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri);
122            args.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap);
123            args.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor);
124            args.putParcelable(EXTRA_ACTION_LAYOUT, mLayout);
125            mLayout.setRefreshViewListener(fragment);
126            args.putString(EXTRA_ACTION_NAME, mName);
127            fragment.setArguments(args);
128            return fragment;
129        }
130
131        public Builder title(String title) {
132            mContentTitle = title;
133            return this;
134        }
135
136        public Builder breadcrumb(String breadcrumb) {
137            mContentBreadcrumb = breadcrumb;
138            return this;
139        }
140
141        public Builder description(String description) {
142            mContentDescription = description;
143            return this;
144        }
145
146        public Builder icon(Drawable icon) {
147            mIcon = icon;
148            return this;
149        }
150
151        public Builder iconUri(Uri iconUri) {
152            mIconUri = iconUri;
153            return this;
154        }
155
156        public Builder iconBitmap(Bitmap iconBitmap) {
157            mIconBitmap = iconBitmap;
158            return this;
159        }
160
161        public Builder iconBackgroundColor(int iconBackgroundColor) {
162            mIconBackgroundColor = iconBackgroundColor;
163            return this;
164        }
165
166        public Builder layout(Layout layout) {
167            mLayout = layout;
168            return this;
169        }
170
171        public Builder name(String name) {
172            mName = name;
173            return this;
174        }
175    }
176
177    public static void add(FragmentManager fm, SettingsLayoutFragment f) {
178        boolean hasDialog = fm.findFragmentByTag(TAG_LEAN_BACK_DIALOG_FRAGMENT) != null;
179        FragmentTransaction ft = fm.beginTransaction();
180
181        if (hasDialog) {
182            ft.setCustomAnimations(ANIMATION_FRAGMENT_ENTER,
183                    ANIMATION_FRAGMENT_EXIT, ANIMATION_FRAGMENT_ENTER_POP,
184                    ANIMATION_FRAGMENT_EXIT_POP);
185            ft.addToBackStack(null);
186        }
187        ft.replace(android.R.id.content, f, TAG_LEAN_BACK_DIALOG_FRAGMENT).commit();
188    }
189
190    private SettingsLayoutAdapter mAdapter;
191    private VerticalGridView mListView;
192    private String mTitle;
193    private String mBreadcrumb;
194    private String mDescription;
195    private Drawable mIcon;
196    private Uri mIconUri;
197    private Bitmap mIconBitmap;
198    private int mIconBackgroundColor = Color.TRANSPARENT;
199    private Layout mLayout;
200    private String mName;
201    private int mSelectedIndex = -1;
202    private boolean mEntryTransitionPerformed;
203    private boolean mIntroAnimationInProgress;
204    private int mAnimateInDuration;
205    private int mAnimateDelay;
206    private int mSecondaryAnimateDelay;
207    private int mSlideInStagger;
208    private int mSlideInDistance;
209    private Handler refreshViewHandler = new Handler();
210
211    private final Runnable mRefreshViewRunnable = new Runnable() {
212        @Override
213        public void run() {
214            mLayout.setSelectedIndex(mListView.getSelectedPosition());
215            mLayout.reloadLayoutRows();
216            mAdapter.setLayoutRows(mLayout.getLayoutRows());
217            mAdapter.setNoAnimateMode();
218            mAdapter.notifyDataSetChanged();
219            mListView.setSelectedPositionSmooth(mLayout.getSelectedIndex());
220        }
221    };
222
223    private final SettingsLayoutAdapter.Listener mLayoutViewRowClicked =
224        new SettingsLayoutAdapter.Listener() {
225            @Override
226            public void onRowClicked(Layout.LayoutRow layoutRow) {
227                onRowViewClicked(layoutRow);
228            }
229        };
230
231    private final SettingsLayoutAdapter.OnFocusListener mLayoutViewOnFocus =
232        new SettingsLayoutAdapter.OnFocusListener() {
233            @Override
234            public void onActionFocused(Layout.LayoutRow action) {
235                if (getActivity() instanceof SettingsLayoutAdapter.OnFocusListener) {
236                    SettingsLayoutAdapter.OnFocusListener listener =
237                            (SettingsLayoutAdapter.OnFocusListener) getActivity();
238                    listener.onActionFocused(action);
239                }
240            }
241        };
242
243    @Override
244    public void onCreate(Bundle savedInstanceState) {
245        android.util.Log.v("SettingsLayoutFragment", "onCreate");
246        super.onCreate(savedInstanceState);
247        Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments();
248        mTitle = state.getString(EXTRA_CONTENT_TITLE);
249        mBreadcrumb = state.getString(EXTRA_CONTENT_BREADCRUMB);
250        mDescription = state.getString(EXTRA_CONTENT_DESCRIPTION);
251        //mIcon = state.getParcelable(EXTRA_CONTENT_ICON_RESOURCE_ID, 0);
252        mIconUri = state.getParcelable(EXTRA_CONTENT_ICON_URI);
253        mIconBitmap = state.getParcelable(EXTRA_CONTENT_ICON_BITMAP);
254        mIconBackgroundColor = state.getInt(EXTRA_CONTENT_ICON_BACKGROUND, Color.TRANSPARENT);
255        mLayout = state.getParcelable(EXTRA_ACTION_LAYOUT);
256        mName = state.getString(EXTRA_ACTION_NAME);
257        mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1);
258        mEntryTransitionPerformed = state.getBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, false);
259    }
260
261    @Override
262    public View onCreateView(LayoutInflater inflater, ViewGroup container,
263            Bundle savedInstanceState) {
264
265        View v = inflater.inflate(R.layout.lb_dialog_fragment, container, false);
266
267        View contentContainer = v.findViewById(R.id.content_fragment);
268        View content = inflater.inflate(R.layout.lb_dialog_content, container, false);
269        ((ViewGroup) contentContainer).addView(content);
270        initializeContentView(content);
271        v.setTag(R.id.content_fragment, content);
272
273        View actionContainer = v.findViewById(R.id.action_fragment);
274        View action = inflater.inflate(R.layout.lb_dialog_action_list, container, false);
275        ((ViewGroup) actionContainer).addView(action);
276        setActionView(action);
277        v.setTag(R.id.action_fragment, action);
278
279        Resources res = getActivity().getResources();
280        mAnimateInDuration = res.getInteger(R.integer.animate_in_duration);
281        mAnimateDelay = res.getInteger(R.integer.animate_delay);
282        mSecondaryAnimateDelay = res.getInteger(R.integer.secondary_animate_delay);
283        mSlideInStagger = res.getInteger(R.integer.slide_in_stagger);
284        mSlideInDistance = res.getInteger(R.integer.slide_in_distance);
285
286        return v;
287    }
288
289    @Override
290    public void onSaveInstanceState(Bundle outState) {
291        super.onSaveInstanceState(outState);
292        outState.putString(EXTRA_CONTENT_TITLE, mTitle);
293        outState.putString(EXTRA_CONTENT_BREADCRUMB, mBreadcrumb);
294        outState.putString(EXTRA_CONTENT_DESCRIPTION, mDescription);
295        //outState.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, mIconResourceId);
296        outState.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri);
297        outState.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap);
298        outState.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor);
299        outState.putParcelable(EXTRA_ACTION_LAYOUT, mLayout);
300        outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
301                (mListView != null) ? mListView.getSelectedPosition() : -1);
302        outState.putString(EXTRA_ACTION_NAME, mName);
303        outState.putBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, mEntryTransitionPerformed);
304    }
305
306    @Override
307    public void onStart() {
308        super.onStart();
309        if (!mEntryTransitionPerformed) {
310            mEntryTransitionPerformed = true;
311            performEntryTransition();
312        }
313    }
314
315    // TODO refactor to get this call as the result of a callback from the Layout.
316    private void updateViews() {
317        View dialogView = getView();
318        View contentView = (View) dialogView.getTag(R.id.content_fragment);
319
320        mBreadcrumb = mLayout.getBreadcrumb();
321        TextView breadcrumbView = (TextView) contentView.getTag(R.id.breadcrumb);
322        breadcrumbView.setText(mBreadcrumb);
323
324        mTitle = mLayout.getTitle();
325        TextView titleView = (TextView) contentView.getTag(R.id.title);
326        titleView.setText(mTitle);
327
328        mDescription = mLayout.getDescription();
329        TextView descriptionView = (TextView) contentView.getTag(R.id.description);
330        descriptionView.setText(mDescription);
331
332        mAdapter.setLayoutRows(mLayout.getLayoutRows());
333        mAdapter.notifyDataSetChanged();
334        mAdapter.setFocusListenerEnabled(false);
335        mListView.setSelectedPositionSmooth(mLayout.getSelectedIndex());
336        mAdapter.setFocusListenerEnabled(true);
337    }
338
339    public void setIcon(int resId) {
340        View dialogView = getView();
341        View contentView = (View) dialogView.getTag(R.id.content_fragment);
342        ImageView iconView = (ImageView) contentView.findViewById(R.id.icon);
343        if (iconView != null) {
344            iconView.setImageResource(resId);
345        }
346    }
347
348    /**
349     * Notification that a part of the model antecedent to the visibile view has changed.
350     */
351    @Override
352    public void onRefreshView() {
353        refreshViewHandler.removeCallbacks(mRefreshViewRunnable);
354        refreshViewHandler.post(mRefreshViewRunnable);
355    }
356
357    /**
358     * Return the currently selected node. The return value may be null, if this is called before
359     * the layout has been rendered for the first time. Clients should check the return value
360     * before using.
361     */
362    @Override
363    public Layout.Node getSelectedNode() {
364        int index = mListView.getSelectedPosition();
365        ArrayList<Layout.LayoutRow> layoutRows = mLayout.getLayoutRows();
366        if (index < layoutRows.size()) {
367            return layoutRows.get(index).getNode();
368        } else {
369            return null;
370        }
371    }
372
373    /**
374     * Process forward key press.
375     */
376    void onRowViewClicked(Layout.LayoutRow layoutRow) {
377        if (layoutRow.isGoBack()) {
378            onBackPressed();
379        } else {
380            Layout.Action action = layoutRow.getUserAction();
381            if (action != null) {
382                Listener actionListener = (Listener) getActivity();
383                if (actionListener != null) {
384                    actionListener.onActionClicked(action);
385                }
386            } else if (mLayout.onClickNavigate(layoutRow)) {
387                mLayout.setParentSelectedIndex(mListView.getSelectedPosition());
388                updateViews();
389            }
390        }
391    }
392
393    /**
394     * Process back key press.
395     */
396    public boolean onBackPressed() {
397        if (mLayout.goBack()) {
398            updateViews();
399            return true;
400        } else {
401            return false;
402        }
403    }
404
405    /**
406     * Client has requested header with {@param title} be selected. If there is no such header
407     * return to the first row.
408     */
409    protected void goBackToTitle(String title) {
410        mLayout.goToTitle(title);
411        updateViews();
412    }
413
414    @Override
415    public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
416        View dialogView = getView();
417        View contentView = (View) dialogView.getTag(R.id.content_fragment);
418        View actionView = (View) dialogView.getTag(R.id.action_fragment);
419        View actionContainerView = dialogView.findViewById(R.id.action_fragment);
420        View titleView = (View) contentView.getTag(R.id.title);
421        View breadcrumbView = (View) contentView.getTag(R.id.breadcrumb);
422        View descriptionView = (View) contentView.getTag(R.id.description);
423        View iconView = (View) contentView.getTag(R.id.icon);
424        View listView = (View) actionView.getTag(R.id.list);
425        View selectorView = (View) actionView.getTag(R.id.selector);
426
427        ArrayList<Animator> animators = new ArrayList<Animator>();
428
429        switch (nextAnim) {
430            case ANIMATION_FRAGMENT_ENTER:
431                animators.add(createSlideLeftInAnimator(titleView));
432                animators.add(createSlideLeftInAnimator(breadcrumbView));
433                animators.add(createSlideLeftInAnimator(descriptionView));
434                animators.add(createSlideLeftInAnimator(iconView));
435                animators.add(createSlideLeftInAnimator(listView));
436                animators.add(createSlideLeftInAnimator(selectorView));
437                break;
438            case ANIMATION_FRAGMENT_EXIT:
439                animators.add(createSlideLeftOutAnimator(titleView));
440                animators.add(createSlideLeftOutAnimator(breadcrumbView));
441                animators.add(createSlideLeftOutAnimator(descriptionView));
442                animators.add(createSlideLeftOutAnimator(iconView));
443                animators.add(createSlideLeftOutAnimator(listView));
444                animators.add(createSlideLeftOutAnimator(selectorView));
445                animators.add(createFadeOutAnimator(actionContainerView));
446                break;
447            case ANIMATION_FRAGMENT_ENTER_POP:
448                animators.add(createSlideRightInAnimator(titleView));
449                animators.add(createSlideRightInAnimator(breadcrumbView));
450                animators.add(createSlideRightInAnimator(descriptionView));
451                animators.add(createSlideRightInAnimator(iconView));
452                animators.add(createSlideRightInAnimator(listView));
453                animators.add(createSlideRightInAnimator(selectorView));
454                break;
455            case ANIMATION_FRAGMENT_EXIT_POP:
456                animators.add(createSlideRightOutAnimator(titleView));
457                animators.add(createSlideRightOutAnimator(breadcrumbView));
458                animators.add(createSlideRightOutAnimator(descriptionView));
459                animators.add(createSlideRightOutAnimator(iconView));
460                animators.add(createSlideRightOutAnimator(listView));
461                animators.add(createSlideRightOutAnimator(selectorView));
462                animators.add(createFadeOutAnimator(actionContainerView));
463                break;
464            default:
465                return super.onCreateAnimator(transit, enter, nextAnim);
466        }
467
468        mEntryTransitionPerformed = true;
469        return createDummyAnimator(dialogView, animators);
470    }
471
472    /**
473     * Called when intro animation is finished.
474     * <p>
475     * If a subclass is going to alter the view, should wait until this is
476     * called.
477     */
478    public void onIntroAnimationFinished() {
479        mIntroAnimationInProgress = false;
480
481        // Display the selector view.
482        View focusedChild = mListView.getFocusedChild();
483        if (focusedChild != null) {
484            View actionView = (View) getView().getTag(R.id.action_fragment);
485            int height = focusedChild.getHeight ();
486            View selectorView = actionView.findViewById(R.id.selector);
487            LayoutParams lp = selectorView.getLayoutParams();
488            lp.height = height;
489            selectorView.setLayoutParams(lp);
490            selectorView.setAlpha (1f);
491        }
492    }
493
494    public boolean isIntroAnimationInProgress() {
495        return mIntroAnimationInProgress;
496    }
497
498    private void initializeContentView(View content) {
499        TextView titleView = (TextView) content.findViewById(R.id.title);
500        TextView breadcrumbView = (TextView) content.findViewById(R.id.breadcrumb);
501        TextView descriptionView = (TextView) content.findViewById(R.id.description);
502        titleView.setText(mTitle);
503        breadcrumbView.setText(mBreadcrumb);
504        descriptionView.setText(mDescription);
505        final ImageView iconImageView = (ImageView) content.findViewById(R.id.icon);
506        iconImageView.setBackgroundColor(mIconBackgroundColor);
507
508        // Force text fields to be focusable when accessibility is enabled.
509        if (AccessibilityHelper.forceFocusableViews(getActivity())) {
510            titleView.setFocusable(true);
511            titleView.setFocusableInTouchMode(true);
512            descriptionView.setFocusable(true);
513            descriptionView.setFocusableInTouchMode(true);
514            breadcrumbView.setFocusable(true);
515            breadcrumbView.setFocusableInTouchMode(true);
516        }
517
518        if (mIcon != null) {
519            iconImageView.setImageDrawable(mIcon);
520            updateViewSize(iconImageView);
521        } else if (mIconBitmap != null) {
522            iconImageView.setImageBitmap(mIconBitmap);
523            updateViewSize(iconImageView);
524        } else if (mIconUri != null) {
525            iconImageView.setVisibility(View.INVISIBLE);
526            /*
527
528            BitmapDownloader bitmapDownloader = BitmapDownloader.getInstance(
529                    content.getContext());
530            mBitmapCallBack = new BitmapCallback() {
531                @Override
532                public void onBitmapRetrieved(Bitmap bitmap) {
533                    if (bitmap != null) {
534                        mIconBitmap = bitmap;
535                        iconImageView.setVisibility(View.VISIBLE);
536                        iconImageView.setImageBitmap(bitmap);
537                        updateViewSize(iconImageView);
538                    }
539                }
540            };
541
542            bitmapDownloader.getBitmap(new BitmapWorkerOptions.Builder(
543                    content.getContext()).resource(mIconUri)
544                    .width(iconImageView.getLayoutParams().width).build(),
545                    mBitmapCallBack);
546            */
547        } else {
548            iconImageView.setVisibility(View.GONE);
549        }
550
551        content.setTag(R.id.title, titleView);
552        content.setTag(R.id.breadcrumb, breadcrumbView);
553        content.setTag(R.id.description, descriptionView);
554        content.setTag(R.id.icon, iconImageView);
555    }
556
557    private void setActionView(View action) {
558        mAdapter = new SettingsLayoutAdapter(mLayoutViewRowClicked, mLayoutViewOnFocus);
559        mAdapter.setLayoutRows(mLayout.getLayoutRows());
560        if (action instanceof VerticalGridView) {
561            mListView = (VerticalGridView) action;
562        } else {
563            mListView = (VerticalGridView) action.findViewById(R.id.list);
564            if (mListView == null) {
565                throw new IllegalArgumentException("No ListView exists.");
566            }
567            mListView.setWindowAlignmentOffset(0);
568            mListView.setWindowAlignmentOffsetPercent(WINDOW_ALIGNMENT_OFFSET_PERCENT);
569            mListView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
570            View selectorView = action.findViewById(R.id.selector);
571            if (selectorView != null) {
572                mListView.setOnScrollListener(new SelectorAnimator(selectorView, mListView));
573            }
574        }
575
576        mListView.requestFocusFromTouch();
577        mListView.setAdapter(mAdapter);
578        int initialSelectedIndex;
579        if (mSelectedIndex >= 0 && mSelectedIndex < mLayout.getLayoutRows().size()) {
580            // "mSelectedIndex" is a valid index and so must have been initialized from a Bundle in
581            // the "onCreate" member and the only way it could be a valid index is if it was saved
582            // by "onSaveInstanceState" since it is initialized to "-1" (an invalid value) in the
583            // constructor.
584            initialSelectedIndex = mSelectedIndex;
585        } else {
586            // First time this fragment is being instantiated, i.e. did not reach here via the
587            // "onSaveInstanceState" route. Initialize the index from the starting index defined
588            // in the "Layout".
589            initialSelectedIndex = mLayout.getSelectedIndex();
590        }
591        mListView.setSelectedPositionSmooth(initialSelectedIndex);
592        action.setTag(R.id.list, mListView);
593        action.setTag(R.id.selector, action.findViewById(R.id.selector));
594    }
595
596    private void updateViewSize(ImageView iconView) {
597        int intrinsicWidth = iconView.getDrawable().getIntrinsicWidth();
598        LayoutParams lp = iconView.getLayoutParams();
599        if (intrinsicWidth > 0) {
600            lp.height = lp.width * iconView.getDrawable().getIntrinsicHeight()
601                    / intrinsicWidth;
602        } else {
603            // If no intrinsic width, then just mke this a square.
604            lp.height = lp.width;
605        }
606    }
607
608    private void fadeIn(View v) {
609        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", FADE_IN_ALPHA_START,
610                FADE_IN_ALPHA_FINISH);
611        alphaAnimator.setDuration(v.getContext().getResources().getInteger(
612                android.R.integer.config_mediumAnimTime));
613        alphaAnimator.start();
614    }
615
616    private void performEntryTransition() {
617        final View dialogView = getView();
618        final View contentView = (View) dialogView.getTag(R.id.content_fragment);
619        final View actionContainerView = dialogView.findViewById(R.id.action_fragment);
620
621        mIntroAnimationInProgress = true;
622
623        // Fade out the old activity.
624        getActivity().overridePendingTransition(0, R.anim.lb_dialog_fade_out);
625
626        int bgColor = contentView.getContext().getResources()
627                .getColor(R.color.lb_dialog_activity_background);
628        final ColorDrawable bgDrawable = new ColorDrawable();
629        bgDrawable.setColor(bgColor);
630        bgDrawable.setAlpha(0);
631        dialogView.setBackground(bgDrawable);
632        dialogView.setVisibility(View.INVISIBLE);
633
634        // We need to defer the remainder of the animation preparation until the first layout has
635        // occurred, as we don't yet know the final location of the icon.
636        contentView.getViewTreeObserver().addOnGlobalLayoutListener(
637                new ViewTreeObserver.OnGlobalLayoutListener() {
638                @Override
639                    public void onGlobalLayout() {
640                        contentView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
641                        // if we buildLayer() at this time, the texture is
642                        // actually not created delay a little so we can make
643                        // sure all hardware layer is created before animation,
644                        // in that way we can avoid the jittering of start
645                        // animation
646                        contentView.postOnAnimationDelayed(mEntryAnimationRunnable, mAnimateDelay);
647                    }
648
649                    Runnable mEntryAnimationRunnable = new Runnable() {
650                            @Override
651                        public void run() {
652                            if (!isAdded()) {
653                                // We have been detached before this could run, so just bail.
654                                return;
655                            }
656
657                            dialogView.setVisibility(View.VISIBLE);
658
659                            // Fade in the activity background protection
660                            ObjectAnimator oa = ObjectAnimator.ofInt(bgDrawable, "alpha", 255);
661                            oa.setDuration(mAnimateInDuration);
662                            oa.setStartDelay(mSecondaryAnimateDelay);
663                            oa.setInterpolator(new DecelerateInterpolator(1.0f));
664                            oa.start();
665
666                            // Fade in and slide in the ContentFragment TextViews from the left.
667                            prepareAndAnimateView((View) contentView.getTag(R.id.title),
668                                    -mSlideInDistance, false);
669                            prepareAndAnimateView((View) contentView.getTag(R.id.breadcrumb),
670                                    -mSlideInDistance, false);
671                            prepareAndAnimateView((View) contentView.getTag(R.id.description),
672                                    -mSlideInDistance, false);
673
674                            // Fade in and slide in the ActionFragment from the right.
675                            prepareAndAnimateView(actionContainerView,
676                                    actionContainerView.getMeasuredWidth(), false);
677                            prepareAndAnimateView((View) contentView.getTag(R.id.icon),
678                                    -mSlideInDistance, true);
679                        }
680                    };
681                });
682    }
683
684    private void prepareAndAnimateView(final View v, float initTransX,
685            final boolean notifyAnimationFinished) {
686        v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
687        v.buildLayer();
688        v.setAlpha(0);
689        v.setTranslationX(initTransX);
690        v.animate().alpha(1f).translationX(0).setDuration(mAnimateInDuration)
691                .setStartDelay(mSecondaryAnimateDelay);
692        v.animate().setInterpolator(new DecelerateInterpolator(1.0f));
693        v.animate().setListener(new AnimatorListenerAdapter() {
694            @Override
695            public void onAnimationEnd(Animator animation) {
696                v.setLayerType(View.LAYER_TYPE_NONE, null);
697                if (notifyAnimationFinished) {
698                    onIntroAnimationFinished();
699                }
700            }
701        });
702        v.animate().start();
703    }
704
705    private Animator createDummyAnimator(final View v, ArrayList<Animator> animators) {
706        final AnimatorSet animatorSet = new AnimatorSet();
707        animatorSet.playTogether(animators);
708        return new UntargetableAnimatorSet(animatorSet);
709    }
710
711    private Animator createAnimator(View v, int resourceId) {
712        Animator animator = AnimatorInflater.loadAnimator(v.getContext(), resourceId);
713        animator.setTarget(v);
714        return animator;
715    }
716
717    private Animator createSlideLeftOutAnimator(View v) {
718        return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_LEFT, -SLIDE_OUT_ANIMATOR_RIGHT,
719               SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA);
720    }
721
722    private Animator createSlideLeftInAnimator(View v) {
723        return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_RIGHT, SLIDE_OUT_ANIMATOR_LEFT,
724                SLIDE_OUT_ANIMATOR_START_ALPHA, SLIDE_OUT_ANIMATOR_END_ALPHA);
725    }
726
727    private Animator createSlideRightInAnimator(View v) {
728        return createTranslateAlphaAnimator(v, -SLIDE_OUT_ANIMATOR_RIGHT, SLIDE_OUT_ANIMATOR_LEFT,
729                SLIDE_OUT_ANIMATOR_START_ALPHA, SLIDE_OUT_ANIMATOR_END_ALPHA);
730    }
731
732    private Animator createSlideRightOutAnimator(View v) {
733        return createTranslateAlphaAnimator(v, SLIDE_OUT_ANIMATOR_LEFT, SLIDE_OUT_ANIMATOR_RIGHT,
734                SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA);
735    }
736
737    private Animator createFadeOutAnimator(View v) {
738        return createAlphaAnimator(v, SLIDE_OUT_ANIMATOR_END_ALPHA, SLIDE_OUT_ANIMATOR_START_ALPHA);
739    }
740
741    private Animator createTranslateAlphaAnimator(View v, float fromTranslateX, float toTranslateX,
742            float fromAlpha, float toAlpha) {
743        ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(v, "translationX", fromTranslateX,
744                toTranslateX);
745        translateAnimator.setDuration(
746                getResources().getInteger(android.R.integer.config_longAnimTime));
747        Animator alphaAnimator = createAlphaAnimator(v, fromAlpha, toAlpha);
748        AnimatorSet animatorSet = new AnimatorSet();
749        animatorSet.play(translateAnimator).with(alphaAnimator);
750        return animatorSet;
751    }
752
753    private Animator createAlphaAnimator(View v, float fromAlpha, float toAlpha) {
754        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", fromAlpha, toAlpha);
755        alphaAnimator.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
756        return alphaAnimator;
757    }
758
759    private static class SelectorAnimator extends RecyclerView.OnScrollListener {
760
761        private final View mSelectorView;
762        private final ViewGroup mParentView;
763        private final int mAnimationDuration;
764        private volatile boolean mFadedOut = true;
765
766        SelectorAnimator(View selectorView, ViewGroup parentView) {
767            mSelectorView = selectorView;
768            mParentView = parentView;
769            mAnimationDuration = selectorView.getContext()
770                    .getResources().getInteger(R.integer.lb_dialog_animation_duration);
771        }
772
773        /**
774         * We want to fade in the selector if we've stopped scrolling on it. If we're scrolling, we
775         * want to ensure to dim the selector if we haven't already. We dim the last highlighted
776         * view so that while a user is scrolling, nothing is highlighted.
777         */
778        @Override
779        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
780            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
781                // The selector starts with a height of 0. In order to scale up from 0 we first
782                // need the set the height to 1 and scale form there.
783                int selectorHeight = mSelectorView.getHeight();
784                if (selectorHeight == 0) {
785                    LayoutParams lp = mSelectorView.getLayoutParams();
786                    lp.height = selectorHeight = mSelectorView.getContext().getResources()
787                            .getDimensionPixelSize(R.dimen.lb_action_fragment_selector_min_height);
788                    mSelectorView.setLayoutParams(lp);
789                }
790                View focusedChild = mParentView.getFocusedChild();
791                if (focusedChild != null) {
792                    float scaleY = (float) focusedChild.getHeight() / selectorHeight;
793                    ViewPropertyAnimator animation = mSelectorView.animate()
794                            .alpha(1f)
795                            .setDuration(mAnimationDuration)
796                            .setInterpolator(new DecelerateInterpolator(2f));
797                    if (mFadedOut) {
798                        // Selector is completely faded out, so we can just scale before fading in.
799                        mSelectorView.setScaleY(scaleY);
800                    } else {
801                        // Selector is not faded out, so we must animate the scale as we fade in.
802                        animation.scaleY(scaleY);
803                    }
804                    animation.start();
805                }
806            } else {
807                mSelectorView.animate()
808                        .alpha(0f)
809                        .setDuration(mAnimationDuration)
810                        .setInterpolator(new DecelerateInterpolator(2f))
811                        .start();
812            }
813        }
814    }
815
816    private static class UntargetableAnimatorSet extends Animator {
817
818        private final AnimatorSet mAnimatorSet;
819
820        UntargetableAnimatorSet(AnimatorSet animatorSet) {
821            mAnimatorSet = animatorSet;
822        }
823
824        @Override
825        public void addListener(Animator.AnimatorListener listener) {
826            mAnimatorSet.addListener(listener);
827        }
828
829        @Override
830        public void cancel() {
831            mAnimatorSet.cancel();
832        }
833
834        @Override
835        public Animator clone() {
836            return mAnimatorSet.clone();
837        }
838
839        @Override
840        public void end() {
841            mAnimatorSet.end();
842        }
843
844        @Override
845        public long getDuration() {
846            return mAnimatorSet.getDuration();
847        }
848
849        @Override
850        public ArrayList<Animator.AnimatorListener> getListeners() {
851            return mAnimatorSet.getListeners();
852        }
853
854        @Override
855        public long getStartDelay() {
856            return mAnimatorSet.getStartDelay();
857        }
858
859        @Override
860        public boolean isRunning() {
861            return mAnimatorSet.isRunning();
862        }
863
864        @Override
865        public boolean isStarted() {
866            return mAnimatorSet.isStarted();
867        }
868
869        @Override
870        public void removeAllListeners() {
871            mAnimatorSet.removeAllListeners();
872        }
873
874        @Override
875        public void removeListener(Animator.AnimatorListener listener) {
876            mAnimatorSet.removeListener(listener);
877        }
878
879        @Override
880        public Animator setDuration(long duration) {
881            return mAnimatorSet.setDuration(duration);
882        }
883
884        @Override
885        public void setInterpolator(TimeInterpolator value) {
886            mAnimatorSet.setInterpolator(value);
887        }
888
889        @Override
890        public void setStartDelay(long startDelay) {
891            mAnimatorSet.setStartDelay(startDelay);
892        }
893
894        @Override
895        public void setTarget(Object target) {
896            // ignore
897        }
898
899        @Override
900        public void setupEndValues() {
901            mAnimatorSet.setupEndValues();
902        }
903
904        @Override
905        public void setupStartValues() {
906            mAnimatorSet.setupStartValues();
907        }
908
909        @Override
910        public void start() {
911            mAnimatorSet.start();
912        }
913    }
914
915}
916