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