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