mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-29 22:41:56 +08:00
Add support for delete animation in LayoutAnimation on Android
Summary: Android follow up to #6779 **Test plan** Tested add/removing views in the UIExample explorer with and without setting a LayoutAnimation. Tested that user interation during the animation is properly disabled.  Closes https://github.com/facebook/react-native/pull/7171 Differential Revision: D3352450 Pulled By: astreet fbshipit-source-id: 233efa041626eb26d99511d12a924e54a10f96cc
This commit is contained in:
committed by
Facebook Github Bot 9
parent
4879f88a75
commit
0fb5ccf6af
@@ -37,9 +37,12 @@ const UIExplorerExampleList = require('./UIExplorerExampleList');
|
||||
const UIExplorerList = require('./UIExplorerList');
|
||||
const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer');
|
||||
const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap');
|
||||
const UIManager = require('UIManager');
|
||||
const URIActionMap = require('./URIActionMap');
|
||||
const View = require('View');
|
||||
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
|
||||
const DRAWER_WIDTH_LEFT = 56;
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.touch.JSResponderHandler;
|
||||
import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController;
|
||||
import com.facebook.react.uimanager.layoutanimation.LayoutAnimationListener;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
|
||||
@@ -294,8 +295,8 @@ public class NativeViewHierarchyManager {
|
||||
@Nullable int[] indicesToRemove,
|
||||
@Nullable ViewAtIndex[] viewsToAdd,
|
||||
@Nullable int[] tagsToDelete) {
|
||||
ViewGroup viewToManage = (ViewGroup) mTagsToViews.get(tag);
|
||||
ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(tag);
|
||||
final ViewGroup viewToManage = (ViewGroup) mTagsToViews.get(tag);
|
||||
final ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(tag);
|
||||
if (viewToManage == null) {
|
||||
throw new IllegalViewOperationException("Trying to manageChildren view with tag " + tag +
|
||||
" which doesn't exist\n detail: " +
|
||||
@@ -344,7 +345,17 @@ public class NativeViewHierarchyManager {
|
||||
viewsToAdd,
|
||||
tagsToDelete));
|
||||
}
|
||||
viewManager.removeViewAt(viewToManage, indicesToRemove[i]);
|
||||
|
||||
View viewToRemove = viewToManage.getChildAt(indexToRemove);
|
||||
|
||||
if (mLayoutAnimator.shouldAnimateLayout(viewToRemove) &&
|
||||
arrayContains(tagsToDelete, viewToRemove.getId())) {
|
||||
// The view will be removed and dropped by the 'delete' layout animation
|
||||
// instead, so do nothing
|
||||
} else {
|
||||
viewManager.removeViewAt(viewToManage, indexToRemove);
|
||||
}
|
||||
|
||||
lastIndexToRemove = indexToRemove;
|
||||
}
|
||||
}
|
||||
@@ -371,7 +382,7 @@ public class NativeViewHierarchyManager {
|
||||
if (tagsToDelete != null) {
|
||||
for (int i = 0; i < tagsToDelete.length; i++) {
|
||||
int tagToDelete = tagsToDelete[i];
|
||||
View viewToDestroy = mTagsToViews.get(tagToDelete);
|
||||
final View viewToDestroy = mTagsToViews.get(tagToDelete);
|
||||
if (viewToDestroy == null) {
|
||||
throw new IllegalViewOperationException(
|
||||
"Trying to destroy unknown view tag: "
|
||||
@@ -383,11 +394,31 @@ public class NativeViewHierarchyManager {
|
||||
viewsToAdd,
|
||||
tagsToDelete));
|
||||
}
|
||||
dropView(viewToDestroy);
|
||||
|
||||
if (mLayoutAnimator.shouldAnimateLayout(viewToDestroy)) {
|
||||
mLayoutAnimator.deleteView(viewToDestroy, new LayoutAnimationListener() {
|
||||
@Override
|
||||
public void onAnimationEnd() {
|
||||
viewManager.removeView(viewToManage, viewToDestroy);
|
||||
dropView(viewToDestroy);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dropView(viewToDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean arrayContains(int[] array, int ele) {
|
||||
for (int curEle : array) {
|
||||
if (curEle == ele) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified version of constructManageChildrenErrorMessage that only deals with adding children
|
||||
* views
|
||||
|
||||
@@ -48,6 +48,15 @@ public abstract class ViewGroupManager <T extends ViewGroup>
|
||||
parent.removeViewAt(index);
|
||||
}
|
||||
|
||||
public void removeView(T parent, View view) {
|
||||
for (int i = 0; i < getChildCount(parent); i++) {
|
||||
if (getChildAt(parent, i) == view) {
|
||||
removeViewAt(parent, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllViews(T parent) {
|
||||
for (int i = getChildCount(parent) - 1; i >= 0; i--) {
|
||||
removeViewAt(parent, i);
|
||||
|
||||
@@ -6,6 +6,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
@@ -25,6 +26,7 @@ public class LayoutAnimationController {
|
||||
|
||||
private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation();
|
||||
private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation();
|
||||
private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation();
|
||||
private boolean mShouldAnimateLayout;
|
||||
|
||||
public void initializeFromConfig(final @Nullable ReadableMap config) {
|
||||
@@ -49,11 +51,17 @@ public class LayoutAnimationController {
|
||||
config.getMap(LayoutAnimationType.UPDATE.toString()), globalDuration);
|
||||
mShouldAnimateLayout = true;
|
||||
}
|
||||
if (config.hasKey(LayoutAnimationType.DELETE.toString())) {
|
||||
mLayoutDeleteAnimation.initializeFromConfig(
|
||||
config.getMap(LayoutAnimationType.DELETE.toString()), globalDuration);
|
||||
mShouldAnimateLayout = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mLayoutCreateAnimation.reset();
|
||||
mLayoutUpdateAnimation.reset();
|
||||
mLayoutDeleteAnimation.reset();
|
||||
mShouldAnimateLayout = false;
|
||||
}
|
||||
|
||||
@@ -65,7 +73,8 @@ public class LayoutAnimationController {
|
||||
|
||||
/**
|
||||
* Update layout of given view, via immediate update or animation depending on the current batch
|
||||
* layout animation configuration supplied during initialization.
|
||||
* layout animation configuration supplied during initialization. Handles create and update
|
||||
* animations.
|
||||
*
|
||||
* @param view the view to update layout of
|
||||
* @param x the new X position for the view
|
||||
@@ -76,9 +85,9 @@ public class LayoutAnimationController {
|
||||
public void applyLayoutUpdate(View view, int x, int y, int width, int height) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Determine which animation to use : if view is initially invisible, use create animation.
|
||||
// If view is becoming invisible, use delete animation. Otherwise, use update animation.
|
||||
// This approach is easier than maintaining a list of tags for recently created/deleted views.
|
||||
// Determine which animation to use : if view is initially invisible, use create animation,
|
||||
// otherwise use update animation. This approach is easier than maintaining a list of tags
|
||||
// for recently created views.
|
||||
AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ?
|
||||
mLayoutCreateAnimation :
|
||||
mLayoutUpdateAnimation;
|
||||
@@ -91,4 +100,54 @@ public class LayoutAnimationController {
|
||||
view.startAnimation(animation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate a view deletion using the layout animation configuration supplied during initialization.
|
||||
*
|
||||
* @param view The view to animate.
|
||||
* @param listener Called once the animation is finished, should be used to
|
||||
* completely remove the view.
|
||||
*/
|
||||
public void deleteView(final View view, final LayoutAnimationListener listener) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
AbstractLayoutAnimation layoutAnimation = mLayoutDeleteAnimation;
|
||||
|
||||
Animation animation = layoutAnimation.createAnimation(
|
||||
view, view.getLeft(), view.getTop(), view.getWidth(), view.getHeight());
|
||||
|
||||
if (animation != null) {
|
||||
disableUserInteractions(view);
|
||||
|
||||
animation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation anim) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation anim) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation anim) {
|
||||
listener.onAnimationEnd();
|
||||
}
|
||||
});
|
||||
|
||||
view.startAnimation(animation);
|
||||
} else {
|
||||
listener.onAnimationEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables user interactions for a view and all it's subviews.
|
||||
*/
|
||||
private void disableUserInteractions(View view) {
|
||||
view.setClickable(false);
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup)view;
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
disableUserInteractions(viewGroup.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation;
|
||||
|
||||
/**
|
||||
* Listener invoked when a layout animation has completed.
|
||||
*/
|
||||
public interface LayoutAnimationListener {
|
||||
void onAnimationEnd();
|
||||
}
|
||||
@@ -7,7 +7,8 @@ package com.facebook.react.uimanager.layoutanimation;
|
||||
*/
|
||||
/* package */ enum LayoutAnimationType {
|
||||
CREATE("create"),
|
||||
UPDATE("update");
|
||||
UPDATE("update"),
|
||||
DELETE("delete");
|
||||
|
||||
private final String mName;
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation;
|
||||
|
||||
/**
|
||||
* Class responsible for handling layout view deletion animation, applied to view whenever a
|
||||
* valid config was supplied for the layout animation of DELETE type.
|
||||
*/
|
||||
/* package */ class LayoutDeleteAnimation extends BaseLayoutAnimation {
|
||||
|
||||
@Override
|
||||
boolean isReverse() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user