BaseDialogFragment.java revision 4ae5c62834131ebd8931ca0d5feae8a7f219065e
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.net.Uri;
26import android.support.v4.view.ViewCompat;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewGroup.LayoutParams;
30import android.view.ViewTreeObserver;
31import android.view.animation.DecelerateInterpolator;
32import android.view.animation.Interpolator;
33import android.widget.ImageView;
34import android.widget.RelativeLayout;
35import android.widget.TextView;
36
37import com.android.tv.settings.R;
38import com.android.tv.settings.util.UriUtils;
39import com.android.tv.settings.util.TransitionImage;
40import com.android.tv.settings.util.TransitionImageAnimation;
41import com.android.tv.settings.widget.FrameLayoutWithShadows;
42
43import java.util.List;
44
45/**
46 * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy
47 */
48public class BaseDialogFragment {
49
50    public static final int ANIMATE_IN_DURATION = 250;
51    public static final int ANIMATE_DELAY = 550;
52    public static final int SLIDE_IN_STAGGER = 100;
53    public static final int SLIDE_IN_DISTANCE = 120;
54
55    public static final String TAG_CONTENT = "content";
56    public static final String TAG_ACTION = "action";
57
58    public int mContentAreaId = R.id.content_fragment;
59    public int mActionAreaId = R.id.action_fragment;
60
61    public FrameLayoutWithShadows mShadowLayer;
62    public boolean mFirstOnStart = true;
63    public boolean mIntroAnimationInProgress = false;
64
65    private LiteFragment mFragment;
66
67    // Related to activity entry transition
68    public ColorDrawable mBgDrawable = new ColorDrawable();
69
70    public BaseDialogFragment(LiteFragment fragment) {
71        mFragment = fragment;
72    }
73
74    public void onActionClicked(Activity activity, Action action) {
75        if (activity instanceof ActionAdapter.Listener) {
76            ((ActionAdapter.Listener) activity).onActionClicked(action);
77        } else {
78            Intent intent = action.getIntent();
79            if (intent != null) {
80                activity.startActivity(intent);
81                activity.finish();
82            }
83        }
84    }
85
86    public void disableEntryAnimation() {
87        mFirstOnStart = false;
88    }
89
90    /**
91     * This method sets the layout property of this class. <br/>
92     * Activities extending {@link DialogFragment} should call this method
93     * before calling {@link #onCreate(Bundle)} if they want to have a
94     * custom view.
95     *
96     * @param contentAreaId id of the content area
97     * @param actionAreaId id of the action area
98     */
99    public void setLayoutProperties(int contentAreaId, int actionAreaId) {
100        mContentAreaId = contentAreaId;
101        mActionAreaId = actionAreaId;
102    }
103
104    public void performEntryTransition(final Activity activity, final ViewGroup contentView,
105            int iconResourceId, Uri iconResourceUri, final ImageView icon, final TextView title,
106            final TextView description, final TextView breadcrumb) {
107        // Pull out the root layout of the dialog and set the background drawable, to be
108        // faded in during the transition.
109        final RelativeLayout twoPane = (RelativeLayout) contentView.getChildAt(0);
110        twoPane.setVisibility(View.INVISIBLE);
111
112        // If the appropriate data is embedded in the intent and there is an icon specified
113        // in the content fragment, we animate the icon from its initial position to the final
114        // position. Otherwise, we perform a simpler transition in which the ActionFragment
115        // slides in and the ContentFragment text fields slide in.
116        mIntroAnimationInProgress = true;
117        boolean lShouldAnimateIcon = true;
118        boolean uriMatch = false;
119        List<TransitionImage> images = TransitionImage.readMultipleFromIntent(
120                activity, activity.getIntent());
121        TransitionImageAnimation ltransitionAnimation = null;
122        final Uri iconUri;
123        final int color;
124        if (images != null && images.size() > 0) {
125            if (iconResourceId != 0) {
126                iconUri = Uri.parse(UriUtils.getAndroidResourceUri(
127                        activity, iconResourceId));
128            } else if (iconResourceUri != null) {
129                iconUri = iconResourceUri;
130            } else {
131                iconUri = null;
132            }
133            TransitionImage src = images.get(0);
134            color = src.getBackground();
135            if (iconUri != null) {
136                ltransitionAnimation = new TransitionImageAnimation(contentView);
137                ltransitionAnimation.addTransitionSource(src);
138                ltransitionAnimation.transitionDurationMs(ANIMATE_IN_DURATION)
139                        .transitionStartDelayMs(0)
140                        .interpolator(new DecelerateInterpolator(1f));
141            }
142        } else {
143            iconUri = null;
144            color = 0;
145        }
146        final TransitionImageAnimation transitionAnimation = ltransitionAnimation;
147
148        // Fade out the old activity, and hard cut the new activity.
149        activity.overridePendingTransition(R.anim.hard_cut_in, R.anim.fade_out);
150
151        int bgColor = mFragment.getResources().getColor(R.color.dialog_activity_background);
152        mBgDrawable.setColor(bgColor);
153        mBgDrawable.setAlpha(0);
154        twoPane.setBackground(mBgDrawable);
155
156        // If we're animating the icon, we create a new ImageView in which to place the embedded
157        // bitmap. We place it in the root layout to match its location in the previous activity.
158        mShadowLayer = (FrameLayoutWithShadows) twoPane.findViewById(R.id.shadow_layout);
159        if (transitionAnimation != null) {
160            transitionAnimation.listener(new TransitionImageAnimation.Listener() {
161                @Override
162                public void onRemovedView(TransitionImage src, TransitionImage dst) {
163                    if (icon != null) {
164                        //want to make sure that users still see at least the source image
165                        // if the dst image is too large to finish downloading before the
166                        // animation is done. Check if the icon is not visible. This mean
167                        // BaseContentFragement have not finish downloading the image yet.
168                        if (icon.getVisibility() != View.VISIBLE) {
169                            icon.setImageDrawable(src.getBitmap());
170                            int intrinsicWidth = icon.getDrawable().getIntrinsicWidth();
171                            LayoutParams lp = icon.getLayoutParams();
172                            lp.height = lp.width * icon.getDrawable().getIntrinsicHeight()
173                                    / intrinsicWidth;
174                            icon.setVisibility(View.VISIBLE);
175                        }
176                        icon.setAlpha(1f);
177                    }
178                    if (mShadowLayer != null) {
179                        mShadowLayer.setShadowsAlpha(1f);
180                    }
181                    onIntroAnimationFinished();
182                }
183            });
184            icon.setAlpha(0f);
185            if (mShadowLayer != null) {
186                mShadowLayer.setShadowsAlpha(0f);
187            }
188        }
189
190        // We need to defer the remainder of the animation preparation until the first
191        // layout has occurred, as we don't yet know the final location of the icon.
192        twoPane.getViewTreeObserver().addOnGlobalLayoutListener(
193                new ViewTreeObserver.OnGlobalLayoutListener() {
194            @Override
195            public void onGlobalLayout() {
196                twoPane.getViewTreeObserver().removeOnGlobalLayoutListener(this);
197                // if we buildLayer() at this time,  the texture is actually not created
198                // delay a little so we can make sure all hardware layer is created before
199                // animation, in that way we can avoid the jittering of start animation
200                twoPane.postOnAnimationDelayed(mEntryAnimationRunnable, ANIMATE_DELAY);
201            }
202            Runnable mEntryAnimationRunnable = new Runnable() {
203                @Override
204                public void run() {
205                    if (!mFragment.isAdded()) {
206                        // We have been detached before this could run, so just bail
207                        return;
208                    }
209
210                    twoPane.setVisibility(View.VISIBLE);
211                    final int secondaryDelay = SLIDE_IN_DISTANCE;
212
213                    // Fade in the activity background protection
214                    ObjectAnimator oa = ObjectAnimator.ofInt(mBgDrawable, "alpha", 255);
215                    oa.setDuration(ANIMATE_IN_DURATION);
216                    oa.setStartDelay(secondaryDelay);
217                    oa.setInterpolator(new DecelerateInterpolator(1.0f));
218                    oa.start();
219
220                    View actionFragmentView = activity.findViewById(mActionAreaId);
221                    boolean isRtl = ViewCompat.getLayoutDirection(contentView) ==
222                            View.LAYOUT_DIRECTION_RTL;
223                    int startDist = isRtl ? SLIDE_IN_DISTANCE : -SLIDE_IN_DISTANCE;
224                    int endDist = isRtl ? -actionFragmentView.getMeasuredWidth() :
225                            actionFragmentView.getMeasuredWidth();
226
227                    // Fade in and slide in the ContentFragment TextViews from the start.
228                    prepareAndAnimateView(title, 0, startDist,
229                            secondaryDelay, ANIMATE_IN_DURATION,
230                            new DecelerateInterpolator(1.0f),
231                            false);
232                    prepareAndAnimateView(breadcrumb, 0, startDist,
233                            secondaryDelay, ANIMATE_IN_DURATION,
234                            new DecelerateInterpolator(1.0f),
235                            false);
236                    prepareAndAnimateView(description, 0,
237                            startDist,
238                            secondaryDelay, ANIMATE_IN_DURATION,
239                            new DecelerateInterpolator(1.0f),
240                            false);
241
242                    // Fade in and slide in the ActionFragment from the end.
243                    prepareAndAnimateView(actionFragmentView, 0,
244                            endDist, secondaryDelay,
245                            ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f),
246                            false);
247
248                    if (icon != null && transitionAnimation != null) {
249                        // now we get the icon view in place,  update the transition target
250                        TransitionImage target = new TransitionImage();
251                        target.setUri(iconUri);
252                        target.createFromImageView(icon);
253                        if (icon.getBackground() instanceof ColorDrawable) {
254                            ColorDrawable d = (ColorDrawable) icon.getBackground();
255                            target.setBackground(d.getColor());
256                        }
257                        transitionAnimation.addTransitionTarget(target);
258                        transitionAnimation.startTransition();
259                    } else if (icon != null) {
260                        prepareAndAnimateView(icon, 0, startDist,
261                                secondaryDelay, ANIMATE_IN_DURATION,
262                                new DecelerateInterpolator(1.0f), true /* is the icon */);
263                        if (mShadowLayer != null) {
264                            mShadowLayer.setShadowsAlpha(0f);
265                        }
266                    }
267                }
268            };
269        });
270    }
271
272    /**
273     * Animates a view.
274     *
275     * @param v              view to animate
276     * @param initAlpha      initial alpha
277     * @param initTransX     initial translation in the X
278     * @param delay          delay in ms
279     * @param duration       duration in ms
280     * @param interpolator   interpolator to be used, can be null
281     * @param isIcon         if {@code true}, this is the main icon being moved
282     */
283    public void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay,
284            int duration, Interpolator interpolator, final boolean isIcon) {
285        if (v != null && v.getWindowToken() != null) {
286            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
287            v.buildLayer();
288            v.setAlpha(initAlpha);
289            v.setTranslationX(initTransX);
290            v.animate().alpha(1f).translationX(0).setDuration(duration).setStartDelay(delay);
291            if (interpolator != null) {
292                v.animate().setInterpolator(interpolator);
293            }
294            v.animate().setListener(new AnimatorListenerAdapter() {
295                @Override
296                public void onAnimationEnd(Animator animation) {
297                    v.setLayerType(View.LAYER_TYPE_NONE, null);
298                    if (isIcon) {
299                        if (mShadowLayer != null) {
300                            mShadowLayer.setShadowsAlpha(1f);
301                        }
302                        onIntroAnimationFinished();
303                    }
304                }
305            });
306            v.animate().start();
307        }
308    }
309
310    /**
311     * Called when intro animation is finished.
312     * <p>
313     * If a subclass is going to alter the view, should wait until this is called.
314     */
315    public void onIntroAnimationFinished() {
316        mIntroAnimationInProgress = false;
317    }
318
319    public boolean isIntroAnimationInProgress() {
320        return mIntroAnimationInProgress;
321    }
322
323    public ColorDrawable getBackgroundDrawable() {
324        return mBgDrawable;
325    }
326
327    public void setBackgroundDrawable(ColorDrawable drawable) {
328        mBgDrawable = drawable;
329    }
330
331}
332