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