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.old;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.app.Activity;
23import android.content.Intent;
24import android.graphics.drawable.ColorDrawable;
25import android.support.v4.view.ViewCompat;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.ViewTreeObserver;
29import android.view.animation.DecelerateInterpolator;
30import android.view.animation.Interpolator;
31import android.widget.ImageView;
32import android.widget.TextView;
33
34import com.android.tv.settings.R;
35import com.android.tv.settings.widget.FrameLayoutWithShadows;
36
37/**
38 * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy
39 */
40public class BaseDialogFragment {
41
42    private static final int ANIMATE_IN_DURATION = 250;
43    private static final int ANIMATE_DELAY = 550;
44    private static final int SLIDE_IN_DISTANCE = 120;
45
46    public static final String TAG_CONTENT = "content";
47    public static final String TAG_ACTION = "action";
48
49    public int mContentAreaId = R.id.content_fragment;
50    public int mActionAreaId = R.id.action_fragment;
51
52    private FrameLayoutWithShadows mShadowLayer;
53    public boolean mFirstOnStart = true;
54    private boolean mIntroAnimationInProgress = false;
55
56    private final LiteFragment mFragment;
57
58    // Related to activity entry transition
59    private ColorDrawable mBgDrawable = new ColorDrawable();
60
61    public BaseDialogFragment(LiteFragment fragment) {
62        mFragment = fragment;
63    }
64
65    public void onActionClicked(Activity activity, Action action) {
66        if (activity instanceof ActionAdapter.Listener) {
67            ((ActionAdapter.Listener) activity).onActionClicked(action);
68        } else {
69            Intent intent = action.getIntent();
70            if (intent != null) {
71                activity.startActivity(intent);
72                activity.finish();
73            }
74        }
75    }
76
77    public void disableEntryAnimation() {
78        mFirstOnStart = false;
79    }
80
81    /**
82     * This method sets the layout property of this class. <br/>
83     * Activities extending {@link DialogFragment} should call this method
84     * before calling {@link #onCreate(Bundle)} if they want to have a
85     * custom view.
86     *
87     * @param contentAreaId id of the content area
88     * @param actionAreaId id of the action area
89     */
90    public void setLayoutProperties(int contentAreaId, int actionAreaId) {
91        mContentAreaId = contentAreaId;
92        mActionAreaId = actionAreaId;
93    }
94
95    public void performEntryTransition(final Activity activity, final ViewGroup contentView,
96            final ImageView icon, final TextView title,
97            final TextView description, final TextView breadcrumb) {
98        // Pull out the root layout of the dialog and set the background drawable, to be
99        // faded in during the transition.
100        final ViewGroup twoPane = (ViewGroup) contentView.getChildAt(0);
101        twoPane.setVisibility(View.INVISIBLE);
102
103        // If the appropriate data is embedded in the intent and there is an icon specified
104        // in the content fragment, we animate the icon from its initial position to the final
105        // position. Otherwise, we perform a simpler transition in which the ActionFragment
106        // slides in and the ContentFragment text fields slide in.
107        mIntroAnimationInProgress = true;
108
109        // Fade out the old activity, and hard cut the new activity.
110        activity.overridePendingTransition(R.anim.hard_cut_in, R.anim.fade_out);
111
112        int bgColor = mFragment.getActivity().getColor(R.color.dialog_activity_background);
113        mBgDrawable.setColor(bgColor);
114        mBgDrawable.setAlpha(0);
115        twoPane.setBackground(mBgDrawable);
116
117        // If we're animating the icon, we create a new ImageView in which to place the embedded
118        // bitmap. We place it in the root layout to match its location in the previous activity.
119        mShadowLayer = (FrameLayoutWithShadows) twoPane.findViewById(R.id.shadow_layout);
120
121        // We need to defer the remainder of the animation preparation until the first
122        // layout has occurred, as we don't yet know the final location of the icon.
123        twoPane.getViewTreeObserver().addOnGlobalLayoutListener(
124                new ViewTreeObserver.OnGlobalLayoutListener() {
125            @Override
126            public void onGlobalLayout() {
127                twoPane.getViewTreeObserver().removeOnGlobalLayoutListener(this);
128                // if we buildLayer() at this time,  the texture is actually not created
129                // delay a little so we can make sure all hardware layer is created before
130                // animation, in that way we can avoid the jittering of start animation
131                twoPane.postOnAnimationDelayed(mEntryAnimationRunnable, ANIMATE_DELAY);
132            }
133
134            final Runnable mEntryAnimationRunnable = new Runnable() {
135                @Override
136                public void run() {
137                    if (!mFragment.isAdded()) {
138                        // We have been detached before this could run, so just bail
139                        return;
140                    }
141
142                    twoPane.setVisibility(View.VISIBLE);
143                    final int secondaryDelay = SLIDE_IN_DISTANCE;
144
145                    // Fade in the activity background protection
146                    ObjectAnimator oa = ObjectAnimator.ofInt(mBgDrawable, "alpha", 255);
147                    oa.setDuration(ANIMATE_IN_DURATION);
148                    oa.setStartDelay(secondaryDelay);
149                    oa.setInterpolator(new DecelerateInterpolator(1.0f));
150                    oa.start();
151
152                    View actionFragmentView = activity.findViewById(mActionAreaId);
153                    boolean isRtl = ViewCompat.getLayoutDirection(contentView) ==
154                            ViewCompat.LAYOUT_DIRECTION_RTL;
155                    int startDist = isRtl ? SLIDE_IN_DISTANCE : -SLIDE_IN_DISTANCE;
156                    int endDist = isRtl ? -actionFragmentView.getMeasuredWidth() :
157                            actionFragmentView.getMeasuredWidth();
158
159                    // Fade in and slide in the ContentFragment TextViews from the start.
160                    prepareAndAnimateView(title, 0, startDist,
161                            secondaryDelay, ANIMATE_IN_DURATION,
162                            new DecelerateInterpolator(1.0f),
163                            false);
164                    prepareAndAnimateView(breadcrumb, 0, startDist,
165                            secondaryDelay, ANIMATE_IN_DURATION,
166                            new DecelerateInterpolator(1.0f),
167                            false);
168                    prepareAndAnimateView(description, 0,
169                            startDist,
170                            secondaryDelay, ANIMATE_IN_DURATION,
171                            new DecelerateInterpolator(1.0f),
172                            false);
173
174                    // Fade in and slide in the ActionFragment from the end.
175                    prepareAndAnimateView(actionFragmentView, 0,
176                            endDist, secondaryDelay,
177                            ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f),
178                            false);
179
180                    if (icon != null) {
181                        prepareAndAnimateView(icon, 0, startDist,
182                                secondaryDelay, ANIMATE_IN_DURATION,
183                                new DecelerateInterpolator(1.0f), true /* is the icon */);
184                        if (mShadowLayer != null) {
185                            mShadowLayer.setShadowsAlpha(0f);
186                        }
187                    }
188                }
189            };
190        });
191    }
192
193    /**
194     * Animates a view.
195     *
196     * @param v              view to animate
197     * @param initAlpha      initial alpha
198     * @param initTransX     initial translation in the X
199     * @param delay          delay in ms
200     * @param duration       duration in ms
201     * @param interpolator   interpolator to be used, can be null
202     * @param isIcon         if {@code true}, this is the main icon being moved
203     */
204    public void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay,
205            int duration, Interpolator interpolator, final boolean isIcon) {
206        if (v != null && v.getWindowToken() != null) {
207            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
208            v.buildLayer();
209            v.setAlpha(initAlpha);
210            v.setTranslationX(initTransX);
211            v.animate().alpha(1f).translationX(0).setDuration(duration).setStartDelay(delay);
212            if (interpolator != null) {
213                v.animate().setInterpolator(interpolator);
214            }
215            v.animate().setListener(new AnimatorListenerAdapter() {
216                @Override
217                public void onAnimationEnd(Animator animation) {
218                    v.setLayerType(View.LAYER_TYPE_NONE, null);
219                    if (isIcon) {
220                        if (mShadowLayer != null) {
221                            mShadowLayer.setShadowsAlpha(1f);
222                        }
223                        onIntroAnimationFinished();
224                    }
225                }
226            });
227            v.animate().start();
228        }
229    }
230
231    /**
232     * Called when intro animation is finished.
233     * <p>
234     * If a subclass is going to alter the view, should wait until this is called.
235     */
236    public void onIntroAnimationFinished() {
237        mIntroAnimationInProgress = false;
238    }
239
240    public boolean isIntroAnimationInProgress() {
241        return mIntroAnimationInProgress;
242    }
243
244    public ColorDrawable getBackgroundDrawable() {
245        return mBgDrawable;
246    }
247
248    public void setBackgroundDrawable(ColorDrawable drawable) {
249        mBgDrawable = drawable;
250    }
251
252}
253