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.TextView; 35 36import com.android.tv.settings.R; 37import com.android.tv.settings.util.TransitionImage; 38import com.android.tv.settings.util.TransitionImageAnimation; 39import com.android.tv.settings.util.UriUtils; 40import com.android.tv.settings.widget.FrameLayoutWithShadows; 41 42import java.util.List; 43 44/** 45 * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy 46 */ 47public 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 68 public BaseDialogFragment(LiteFragment fragment) { 69 mFragment = fragment; 70 } 71 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 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 */ 97 public void setLayoutProperties(int contentAreaId, int actionAreaId) { 98 mContentAreaId = contentAreaId; 99 mActionAreaId = actionAreaId; 100 } 101 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 */ 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 */ 312 public void onIntroAnimationFinished() { 313 mIntroAnimationInProgress = false; 314 } 315 316 public boolean isIntroAnimationInProgress() { 317 return mIntroAnimationInProgress; 318 } 319 320 public ColorDrawable getBackgroundDrawable() { 321 return mBgDrawable; 322 } 323 324 public void setBackgroundDrawable(ColorDrawable drawable) { 325 mBgDrawable = drawable; 326 } 327 328} 329