mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-05 22:50:32 +08:00
This change fixes two crashes related to fragment library. The first issue was a crash caused by return transaction started while the previous transaction was ongoing (e.g., quickly adding new screen on top of the stack and immediately dismissing it). The main fix was applied in the fragment library and therefore as a part of this change we update the dependency to fragment:1.2.1 which is the current latest stable version. As a result of the fragment library change we started observing other issue. The second issue was caused by the fact that under certain circumstances the view associated with a fragment couldn't been added despite it still being attached to a parent. This was resulting in a crash. This change adds a cleanup code that properly detaches the view: we do it in onCreateView but also when the fragment destroys its view (in onViewDestroy callback). The latter is necessary because when fragments are restored the order of onCreateView calls is reversed which causes inner views to attach despite their fragment managers not being initialized.
182 lines
5.7 KiB
Java
182 lines
5.7 KiB
Java
package com.swmansion.rnscreens;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.graphics.Color;
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewParent;
|
|
import android.view.animation.Animation;
|
|
import android.widget.LinearLayout;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.widget.Toolbar;
|
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
import androidx.fragment.app.Fragment;
|
|
|
|
import com.facebook.react.uimanager.PixelUtil;
|
|
import com.google.android.material.appbar.AppBarLayout;
|
|
|
|
public class ScreenStackFragment extends ScreenFragment {
|
|
|
|
private static class NotifyingCoordinatorLayout extends CoordinatorLayout {
|
|
|
|
private final ScreenFragment mFragment;
|
|
|
|
public NotifyingCoordinatorLayout(@NonNull Context context, ScreenFragment fragment) {
|
|
super(context);
|
|
mFragment = fragment;
|
|
}
|
|
|
|
@Override
|
|
public void startAnimation(Animation animation) {
|
|
super.startAnimation(animation);
|
|
}
|
|
|
|
@Override
|
|
protected void onAnimationEnd() {
|
|
super.onAnimationEnd();
|
|
mFragment.onViewAnimationEnd();
|
|
}
|
|
}
|
|
|
|
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
|
|
|
private AppBarLayout mAppBarLayout;
|
|
private Toolbar mToolbar;
|
|
private boolean mShadowHidden;
|
|
private CoordinatorLayout mScreenRootView;
|
|
|
|
@SuppressLint("ValidFragment")
|
|
public ScreenStackFragment(Screen screenView) {
|
|
super(screenView);
|
|
}
|
|
|
|
public void removeToolbar() {
|
|
if (mAppBarLayout != null) {
|
|
((CoordinatorLayout) getView()).removeView(mAppBarLayout);
|
|
}
|
|
}
|
|
|
|
public void setToolbar(Toolbar toolbar) {
|
|
if (mAppBarLayout != null) {
|
|
mAppBarLayout.addView(toolbar);
|
|
}
|
|
mToolbar = toolbar;
|
|
AppBarLayout.LayoutParams params = new AppBarLayout.LayoutParams(
|
|
AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT);
|
|
params.setScrollFlags(0);
|
|
mToolbar.setLayoutParams(params);
|
|
}
|
|
|
|
public void setToolbarShadowHidden(boolean hidden) {
|
|
if (mShadowHidden != hidden) {
|
|
mAppBarLayout.setTargetElevation(hidden ? 0 : TOOLBAR_ELEVATION);
|
|
mShadowHidden = hidden;
|
|
}
|
|
}
|
|
|
|
public void onStackUpdate() {
|
|
View child = mScreenView.getChildAt(0);
|
|
if (child instanceof ScreenStackHeaderConfig) {
|
|
((ScreenStackHeaderConfig) child).onUpdate();
|
|
}
|
|
}
|
|
|
|
private CoordinatorLayout configureView() {
|
|
CoordinatorLayout view = new NotifyingCoordinatorLayout(getContext(), this);
|
|
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
|
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
|
|
params.setBehavior(new AppBarLayout.ScrollingViewBehavior());
|
|
mScreenView.setLayoutParams(params);
|
|
view.addView(mScreenView);
|
|
|
|
mAppBarLayout = new AppBarLayout(getContext());
|
|
// By default AppBarLayout will have a background color set but since we cover the whole layout
|
|
// with toolbar (that can be semi-transparent) the bar layout background color does not pay a
|
|
// role. On top of that it breaks screens animations when alfa offscreen compositing is off
|
|
// (which is the default)
|
|
mAppBarLayout.setBackgroundColor(Color.TRANSPARENT);
|
|
mAppBarLayout.setLayoutParams(new AppBarLayout.LayoutParams(
|
|
AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT));
|
|
view.addView(mAppBarLayout);
|
|
|
|
if (mToolbar != null) {
|
|
mAppBarLayout.addView(mToolbar);
|
|
}
|
|
|
|
return view;
|
|
}
|
|
|
|
@Override
|
|
public void onViewAnimationEnd() {
|
|
super.onViewAnimationEnd();
|
|
notifyViewAppearTransitionEnd();
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
|
|
if (enter && transit == 0) {
|
|
// this means that the fragment will appear without transition, in this case onViewAnimationEnd
|
|
// won't be called and we need to notify stack directly from here.
|
|
notifyViewAppearTransitionEnd();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void notifyViewAppearTransitionEnd() {
|
|
ViewParent screenStack = getView().getParent();
|
|
if (screenStack instanceof ScreenStack) {
|
|
((ScreenStack) screenStack).onViewAppearTransitionEnd();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public View onCreateView(LayoutInflater inflater,
|
|
@Nullable ViewGroup container,
|
|
@Nullable Bundle savedInstanceState) {
|
|
if (mScreenRootView == null) {
|
|
mScreenRootView = configureView();
|
|
}
|
|
|
|
return recycleView(mScreenRootView);
|
|
}
|
|
|
|
public boolean isDismissable() {
|
|
return mScreenView.isGestureEnabled();
|
|
}
|
|
|
|
public boolean canNavigateBack() {
|
|
ScreenContainer container = mScreenView.getContainer();
|
|
if (container instanceof ScreenStack) {
|
|
if (((ScreenStack) container).getRootScreen() == getScreen()) {
|
|
// this screen is the root of the container, if it is nested we can check parent container
|
|
// if it is also a root or not
|
|
Fragment parentFragment = getParentFragment();
|
|
if (parentFragment instanceof ScreenStackFragment) {
|
|
return ((ScreenStackFragment) parentFragment).canNavigateBack();
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
|
|
}
|
|
}
|
|
|
|
public void dismiss() {
|
|
ScreenContainer container = mScreenView.getContainer();
|
|
if (container instanceof ScreenStack) {
|
|
((ScreenStack) container).dismiss(this);
|
|
} else {
|
|
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
|
|
}
|
|
}
|
|
}
|