/*
* 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.support.v4.view.ViewCompat;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.tv.settings.R;
import com.android.tv.settings.widget.FrameLayoutWithShadows;
/**
* This class exists to make extending both v4 DialogFragments and regular DialogFragments easy
*/
public class BaseDialogFragment {
private static final int ANIMATE_IN_DURATION = 250;
private static final int ANIMATE_DELAY = 550;
private 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;
private FrameLayoutWithShadows mShadowLayer;
public boolean mFirstOnStart = true;
private boolean mIntroAnimationInProgress = false;
private final LiteFragment mFragment;
// Related to activity entry transition
private 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,
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 ViewGroup twoPane = (ViewGroup) 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;
// 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.getActivity().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);
// 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);
}
final 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();
View actionFragmentView = activity.findViewById(mActionAreaId);
boolean isRtl = ViewCompat.getLayoutDirection(contentView) ==
ViewCompat.LAYOUT_DIRECTION_RTL;
int startDist = isRtl ? SLIDE_IN_DISTANCE : -SLIDE_IN_DISTANCE;
int endDist = isRtl ? -actionFragmentView.getMeasuredWidth() :
actionFragmentView.getMeasuredWidth();
// Fade in and slide in the ContentFragment TextViews from the start.
prepareAndAnimateView(title, 0, startDist,
secondaryDelay, ANIMATE_IN_DURATION,
new DecelerateInterpolator(1.0f),
false);
prepareAndAnimateView(breadcrumb, 0, startDist,
secondaryDelay, ANIMATE_IN_DURATION,
new DecelerateInterpolator(1.0f),
false);
prepareAndAnimateView(description, 0,
startDist,
secondaryDelay, ANIMATE_IN_DURATION,
new DecelerateInterpolator(1.0f),
false);
// Fade in and slide in the ActionFragment from the end.
prepareAndAnimateView(actionFragmentView, 0,
endDist, secondaryDelay,
ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f),
false);
if (icon != null) {
prepareAndAnimateView(icon, 0, startDist,
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; } }