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