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