/* * Copyright (C) 2013 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 android.transition; import android.animation.Animator; import android.view.View; import android.view.ViewGroup; /** * This transition tracks changes to the visibility of target views in the * start and end scenes. Visibility is determined not just by the * {@link View#setVisibility(int)} state of views, but also whether * views exist in the current view hierarchy. The class is intended to be a * utility for subclasses such as {@link Fade}, which use this visibility * information to determine the specific animations to run when visibility * changes occur. Subclasses should implement one or both of the methods * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}, * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}, * *
Note that a view's visibility change is determined by both whether the view * itself is changing and whether its parent hierarchy's visibility is changing. * That is, a view that appears in the end scene will only trigger a call to * {@link #onAppear(android.view.ViewGroup, TransitionValues, int, TransitionValues, int) * appear()} if its parent hierarchy was stable between the start and end scenes. * This is done to avoid causing a visibility transition on every node in a hierarchy * when only the top-most node is the one that should be transitioned in/out. * Stability is determined by either the parent hierarchy views being the same * between scenes or, if scenes are inflated from layout resource files and thus * have result in different view instances, if the views represented by * the ids of those parents are stable. This means that visibility determination * is more effective with inflated view hierarchies if ids are used. * The exception to this is when the visibility subclass transition is * targeted at specific views, in which case the visibility of parent views * is ignored.
*/ public abstract class Visibility extends Transition { private static final String PROPNAME_VISIBILITY = "android:visibility:visibility"; private static final String PROPNAME_PARENT = "android:visibility:parent"; private static final String[] sTransitionProperties = { PROPNAME_VISIBILITY, PROPNAME_PARENT, }; private static class VisibilityInfo { boolean visibilityChange; boolean fadeIn; int startVisibility; int endVisibility; ViewGroup startParent; ViewGroup endParent; } // Temporary structure, used in calculating state in setup() and play() private VisibilityInfo mTmpVisibilityInfo = new VisibilityInfo(); @Override public String[] getTransitionProperties() { return sTransitionProperties; } private void captureValues(TransitionValues transitionValues) { int visibility = transitionValues.view.getVisibility(); transitionValues.values.put(PROPNAME_VISIBILITY, visibility); transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); } @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } /** * Returns whether the view is 'visible' according to the given values * object. This is determined by testing the same properties in the values * object that are used to determine whether the object is appearing or * disappearing in the {@link * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)} * method. This method can be called by, for example, subclasses that want * to know whether the object is visible in the same way that Visibility * determines it for the actual animation. * * @param values The TransitionValues object that holds the information by * which visibility is determined. * @return True if the view reference byvalues
is visible,
* false otherwise.
*/
public boolean isVisible(TransitionValues values) {
if (values == null) {
return false;
}
int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
View parent = (View) values.values.get(PROPNAME_PARENT);
return visibility == View.VISIBLE && parent != null;
}
/**
* Tests whether the hierarchy, up to the scene root, changes visibility between
* start and end scenes. This is done to ensure that a view that changes visibility
* is only animated if that view's parent was stable between scenes; we should not
* fade an entire hierarchy, but rather just the top-most node in the hierarchy that
* changed visibility. Note that both the start and end parents are passed in
* because the instances may differ for the same view due to layout inflation
* between scenes.
*
* @param sceneRoot The root of the scene hierarchy
* @param startView The container view in the start scene
* @param endView The container view in the end scene
* @return true if the parent hierarchy experienced a visibility change, false
* otherwise
*/
private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup startView,
ViewGroup endView) {
if (startView == sceneRoot || endView == sceneRoot) {
return false;
}
TransitionValues startValues = startView != null ?
getTransitionValues(startView, true) : getTransitionValues(endView, true);
TransitionValues endValues = endView != null ?
getTransitionValues(endView, false) : getTransitionValues(startView, false);
if (startValues == null || endValues == null) {
return true;
}
Integer visibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
int startVisibility = (visibility != null) ? visibility : -1;
ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
visibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
int endVisibility = (visibility != null) ? visibility : -1;
ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
if (startVisibility != endVisibility || startParent != endParent) {
return true;
}
if (startParent != null || endParent != null) {
return isHierarchyVisibilityChanging(sceneRoot, startParent, endParent);
}
return false;
}
private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
TransitionValues endValues) {
final VisibilityInfo visInfo = mTmpVisibilityInfo;
visInfo.visibilityChange = false;
visInfo.fadeIn = false;
if (startValues != null) {
visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
} else {
visInfo.startVisibility = -1;
visInfo.startParent = null;
}
if (endValues != null) {
visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
} else {
visInfo.endVisibility = -1;
visInfo.endParent = null;
}
if (startValues != null && endValues != null) {
if (visInfo.startVisibility == visInfo.endVisibility &&
visInfo.startParent == visInfo.endParent) {
return visInfo;
} else {
if (visInfo.startVisibility != visInfo.endVisibility) {
if (visInfo.startVisibility == View.VISIBLE) {
visInfo.fadeIn = false;
visInfo.visibilityChange = true;
} else if (visInfo.endVisibility == View.VISIBLE) {
visInfo.fadeIn = true;
visInfo.visibilityChange = true;
}
// no visibilityChange if going between INVISIBLE and GONE
} else if (visInfo.startParent != visInfo.endParent) {
if (visInfo.endParent == null) {
visInfo.fadeIn = false;
visInfo.visibilityChange = true;
} else if (visInfo.startParent == null) {
visInfo.fadeIn = true;
visInfo.visibilityChange = true;
}
}
}
}
if (startValues == null) {
visInfo.fadeIn = true;
visInfo.visibilityChange = true;
} else if (endValues == null) {
visInfo.fadeIn = false;
visInfo.visibilityChange = true;
}
return visInfo;
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
if (visInfo.visibilityChange) {
// Only transition views that are either targets of this transition
// or whose parent hierarchies remain stable between scenes
boolean isTarget = false;
if (mTargets.size() > 0 || mTargetIds.size() > 0) {
View startView = startValues != null ? startValues.view : null;
View endView = endValues != null ? endValues.view : null;
int startId = startView != null ? startView.getId() : -1;
int endId = endView != null ? endView.getId() : -1;
isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId);
}
if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null) &&
!isHierarchyVisibilityChanging(sceneRoot,
visInfo.startParent, visInfo.endParent))) {
if (visInfo.fadeIn) {
return onAppear(sceneRoot, startValues, visInfo.startVisibility,
endValues, visInfo.endVisibility);
} else {
return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
endValues, visInfo.endVisibility
);
}
}
}
return null;
}
/**
* The default implementation of this method does nothing. Subclasses
* should override if they need to create an Animator when targets appear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
*
* @param sceneRoot The root of the transition hierarchy
* @param startValues The target values in the start scene
* @param startVisibility The target visibility in the start scene
* @param endValues The target values in the end scene
* @param endVisibility The target visibility in the end scene
* @return An Animator to be started at the appropriate time in the
* overall transition for this scene change. A null value means no animation
* should be run.
*/
public Animator onAppear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
return null;
}
/**
* The default implementation of this method does nothing. Subclasses
* should override if they need to create an Animator when targets disappear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
*
*
* @param sceneRoot The root of the transition hierarchy
* @param startValues The target values in the start scene
* @param startVisibility The target visibility in the start scene
* @param endValues The target values in the end scene
* @param endVisibility The target visibility in the end scene
* @return An Animator to be started at the appropriate time in the
* overall transition for this scene change. A null value means no animation
* should be run.
*/
public Animator onDisappear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
return null;
}
}