/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.settings.dialog.old; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import com.android.tv.settings.R; import com.android.tv.settings.util.UriUtils; import com.android.tv.settings.util.TransitionImage; import com.android.tv.settings.util.TransitionImageAnimation; import com.android.tv.settings.widget.FrameLayoutWithShadows; import java.util.List; /** * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy */ public class BaseDialogFragment { public static final int ANIMATE_IN_DURATION = 250; public static final int ANIMATE_DELAY = 550; public static final int SLIDE_IN_STAGGER = 100; public static final int SLIDE_IN_DISTANCE = 120; public static final String TAG_CONTENT = "content"; public static final String TAG_ACTION = "action"; public int mContentAreaId = R.id.content_fragment; public int mActionAreaId = R.id.action_fragment; public FrameLayoutWithShadows mShadowLayer; public boolean mFirstOnStart = true; public boolean mIntroAnimationInProgress = false; private LiteFragment mFragment; // Related to activity entry transition public ColorDrawable mBgDrawable = new ColorDrawable(); public BaseDialogFragment(LiteFragment fragment) { mFragment = fragment; } public void onActionClicked(Activity activity, Action action) { if (activity instanceof ActionAdapter.Listener) { ((ActionAdapter.Listener) activity).onActionClicked(action); } else { Intent intent = action.getIntent(); if (intent != null) { activity.startActivity(intent); activity.finish(); } } } public void disableEntryAnimation() { mFirstOnStart = false; } /** * This method sets the layout property of this class.
* Activities extending {@link DialogFragment} should call this method * before calling {@link #onCreate(Bundle)} if they want to have a * custom view. * * @param contentAreaId id of the content area * @param actionAreaId id of the action area */ public void setLayoutProperties(int contentAreaId, int actionAreaId) { mContentAreaId = contentAreaId; mActionAreaId = actionAreaId; } public void performEntryTransition(final Activity activity, final ViewGroup contentView, int iconResourceId, Uri iconResourceUri, final ImageView icon, final TextView title, final TextView description, final TextView breadcrumb) { // Pull out the root layout of the dialog and set the background drawable, to be // faded in during the transition. final RelativeLayout twoPane = (RelativeLayout) contentView.getChildAt(0); twoPane.setVisibility(View.INVISIBLE); // If the appropriate data is embedded in the intent and there is an icon specified // in the content fragment, we animate the icon from its initial position to the final // position. Otherwise, we perform a simpler transition in which the ActionFragment // slides in and the ContentFragment text fields slide in. mIntroAnimationInProgress = true; boolean lShouldAnimateIcon = true; boolean uriMatch = false; List images = TransitionImage.readMultipleFromIntent( activity, activity.getIntent()); TransitionImageAnimation ltransitionAnimation = null; final Uri iconUri; final int color; if (images != null && images.size() > 0) { if (iconResourceId != 0) { iconUri = Uri.parse(UriUtils.getAndroidResourceUri( activity, iconResourceId)); } else if (iconResourceUri != null) { iconUri = iconResourceUri; } else { iconUri = null; } TransitionImage src = images.get(0); color = src.getBackground(); if (iconUri != null) { ltransitionAnimation = new TransitionImageAnimation(contentView); ltransitionAnimation.addTransitionSource(src); ltransitionAnimation.transitionDurationMs(ANIMATE_IN_DURATION) .transitionStartDelayMs(0) .interpolator(new DecelerateInterpolator(1f)); } } else { iconUri = null; color = 0; } final TransitionImageAnimation transitionAnimation = ltransitionAnimation; // Fade out the old activity, and hard cut the new activity. activity.overridePendingTransition(R.anim.hard_cut_in, R.anim.fade_out); int bgColor = mFragment.getResources().getColor(R.color.dialog_activity_background); mBgDrawable.setColor(bgColor); mBgDrawable.setAlpha(0); twoPane.setBackground(mBgDrawable); // If we're animating the icon, we create a new ImageView in which to place the embedded // bitmap. We place it in the root layout to match its location in the previous activity. mShadowLayer = (FrameLayoutWithShadows) twoPane.findViewById(R.id.shadow_layout); if (transitionAnimation != null) { transitionAnimation.listener(new TransitionImageAnimation.Listener() { @Override public void onRemovedView(TransitionImage src, TransitionImage dst) { if (icon != null) { //want to make sure that users still see at least the source image // if the dst image is too large to finish downloading before the // animation is done. Check if the icon is not visible. This mean // BaseContentFragement have not finish downloading the image yet. if (icon.getVisibility() != View.VISIBLE) { icon.setImageDrawable(src.getBitmap()); int intrinsicWidth = icon.getDrawable().getIntrinsicWidth(); LayoutParams lp = icon.getLayoutParams(); lp.height = lp.width * icon.getDrawable().getIntrinsicHeight() / intrinsicWidth; icon.setVisibility(View.VISIBLE); } icon.setAlpha(1f); } if (mShadowLayer != null) { mShadowLayer.setShadowsAlpha(1f); } onIntroAnimationFinished(); } }); icon.setAlpha(0f); if (mShadowLayer != null) { mShadowLayer.setShadowsAlpha(0f); } } // We need to defer the remainder of the animation preparation until the first // layout has occurred, as we don't yet know the final location of the icon. twoPane.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { twoPane.getViewTreeObserver().removeOnGlobalLayoutListener(this); // if we buildLayer() at this time, the texture is actually not created // delay a little so we can make sure all hardware layer is created before // animation, in that way we can avoid the jittering of start animation twoPane.postOnAnimationDelayed(mEntryAnimationRunnable, ANIMATE_DELAY); } Runnable mEntryAnimationRunnable = new Runnable() { @Override public void run() { if (!mFragment.isAdded()) { // We have been detached before this could run, so just bail return; } twoPane.setVisibility(View.VISIBLE); final int secondaryDelay = SLIDE_IN_DISTANCE; // Fade in the activity background protection ObjectAnimator oa = ObjectAnimator.ofInt(mBgDrawable, "alpha", 255); oa.setDuration(ANIMATE_IN_DURATION); oa.setStartDelay(secondaryDelay); oa.setInterpolator(new DecelerateInterpolator(1.0f)); oa.start(); // Fade in and slide in the ContentFragment TextViews from the left. prepareAndAnimateView(title, 0, - SLIDE_IN_DISTANCE, secondaryDelay, ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), false); prepareAndAnimateView(breadcrumb, 0, - SLIDE_IN_DISTANCE, secondaryDelay, ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), false); prepareAndAnimateView(description, 0, - SLIDE_IN_DISTANCE, secondaryDelay, ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), false); // Fade in and slide in the ActionFragment from the right. View actionFragmentView = activity.findViewById(mActionAreaId); prepareAndAnimateView(actionFragmentView, 0, actionFragmentView.getMeasuredWidth(), secondaryDelay, ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), false); if (icon != null && transitionAnimation != null) { // now we get the icon view in place, update the transition target TransitionImage target = new TransitionImage(); target.setUri(iconUri); target.createFromImageView(icon); if (icon.getBackground() instanceof ColorDrawable) { ColorDrawable d = (ColorDrawable) icon.getBackground(); target.setBackground(d.getColor()); } transitionAnimation.addTransitionTarget(target); transitionAnimation.startTransition(); } else if (icon != null) { prepareAndAnimateView(icon, 0, - SLIDE_IN_DISTANCE, secondaryDelay, ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f), true /* is the icon */); if (mShadowLayer != null) { mShadowLayer.setShadowsAlpha(0f); } } } }; }); } /** * Animates a view. * * @param v view to animate * @param initAlpha initial alpha * @param initTransX initial translation in the X * @param delay delay in ms * @param duration duration in ms * @param interpolator interpolator to be used, can be null * @param isIcon if {@code true}, this is the main icon being moved */ public void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay, int duration, Interpolator interpolator, final boolean isIcon) { if (v != null && v.getWindowToken() != null) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); v.buildLayer(); v.setAlpha(initAlpha); v.setTranslationX(initTransX); v.animate().alpha(1f).translationX(0).setDuration(duration).setStartDelay(delay); if (interpolator != null) { v.animate().setInterpolator(interpolator); } v.animate().setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { v.setLayerType(View.LAYER_TYPE_NONE, null); if (isIcon) { if (mShadowLayer != null) { mShadowLayer.setShadowsAlpha(1f); } onIntroAnimationFinished(); } } }); v.animate().start(); } } /** * Called when intro animation is finished. *

* If a subclass is going to alter the view, should wait until this is called. */ public void onIntroAnimationFinished() { mIntroAnimationInProgress = false; } public boolean isIntroAnimationInProgress() { return mIntroAnimationInProgress; } public ColorDrawable getBackgroundDrawable() { return mBgDrawable; } public void setBackgroundDrawable(ColorDrawable drawable) { mBgDrawable = drawable; } }