mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 17:23:42 +08:00
Initial commit.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.arch.lifecycle.Lifecycle;
|
||||
import android.arch.lifecycle.LifecycleObserver;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import com.facebook.react.modules.core.ChoreographerCompat;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class LifecycleHelper {
|
||||
|
||||
public static @Nullable Fragment findNearestScreenFragmentAncestor(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
while (parent != null && !(parent instanceof Screen)) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (parent != null) {
|
||||
return ((Screen) parent).getFragment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<View, Lifecycle> mViewToLifecycleMap = new HashMap<>();
|
||||
private View.OnLayoutChangeListener mRegisterOnLayoutChange = new View.OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
|
||||
registerViewWithLifecycleOwner(view);
|
||||
view.removeOnLayoutChangeListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
private void registerViewWithLifecycleOwner(View view) {
|
||||
Fragment parent = findNearestScreenFragmentAncestor(view);
|
||||
if (parent != null && view instanceof LifecycleObserver) {
|
||||
Lifecycle lifecycle = parent.getLifecycle();
|
||||
lifecycle.addObserver((LifecycleObserver) view);
|
||||
mViewToLifecycleMap.put(view, lifecycle);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends View & LifecycleObserver> void register(T view) {
|
||||
// we need to wait until view is mounted in the hierarchy as this method is called only at the
|
||||
// moment of the view creation. In order to register lifecycle observer we need to find ancestor
|
||||
// of type Screen and this can only happen when the view is properly attached. We rely on Android's
|
||||
// onLayout callback being triggered when the view gets added to the hierarchy and only then we
|
||||
// attempt to locate lifecycle owner ancestor.
|
||||
view.addOnLayoutChangeListener(mRegisterOnLayoutChange);
|
||||
}
|
||||
|
||||
public <T extends View & LifecycleObserver> void unregister(T view) {
|
||||
Lifecycle lifecycle = mViewToLifecycleMap.get(view);
|
||||
if (lifecycle != null) {
|
||||
lifecycle.removeObserver(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RNScreenPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
new ScreenContainerViewManager(),
|
||||
new ScreenStackViewManager(),
|
||||
new ScreenViewManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
72
android/src/main/java/com/swmansion/rnscreens/Screen.java
Normal file
72
android/src/main/java/com/swmansion/rnscreens/Screen.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class Screen extends ViewGroup {
|
||||
|
||||
public static class ScreenFragment extends Fragment {
|
||||
|
||||
private Screen mScreenView;
|
||||
|
||||
public ScreenFragment() {
|
||||
throw new IllegalStateException("Screen fragments should never be restored");
|
||||
}
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public ScreenFragment(Screen screenView) {
|
||||
super();
|
||||
mScreenView = screenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return mScreenView;
|
||||
}
|
||||
}
|
||||
|
||||
private final Fragment mFragment;
|
||||
private @Nullable ScreenContainer mContainer;
|
||||
private boolean mActive;
|
||||
|
||||
public Screen(Context context) {
|
||||
super(context);
|
||||
mFragment = new ScreenFragment(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
protected void setContainer(@Nullable ScreenContainer mContainer) {
|
||||
this.mContainer = mContainer;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
return mContainer;
|
||||
}
|
||||
|
||||
protected Fragment getFragment() {
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
mActive = active;
|
||||
if (mContainer != null) {
|
||||
mContainer.notifyChildUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return mActive;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.ChoreographerCompat;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScreenContainer extends ViewGroup {
|
||||
|
||||
private final ArrayList<Screen> mScreens = new ArrayList<>();
|
||||
private final Set<Screen> mActiveScreens = new HashSet<>();
|
||||
private final FragmentManager mFragmentManager;
|
||||
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
|
||||
private ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
updateIfNeeded();
|
||||
}
|
||||
};
|
||||
|
||||
public ScreenContainer(Context context) {
|
||||
super(context);
|
||||
Activity activity = ((ReactContext) context).getCurrentActivity();
|
||||
if (activity instanceof FragmentActivity) {
|
||||
mFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"In order to use RNScreen components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
protected void markUpdated() {
|
||||
if (!mNeedUpdate) {
|
||||
mNeedUpdate = true;
|
||||
// enqueue callback of NATIVE_ANIMATED_MODULE type as all view operations are executed in
|
||||
// DISPATCH_UI type and we want the callback to be called right after in the same frame.
|
||||
ReactChoreographer.getInstance().postFrameCallback(
|
||||
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
||||
mFrameCallback);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyChildUpdate() {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void addScreen(Screen screen, int index) {
|
||||
mScreens.add(index, screen);
|
||||
screen.setContainer(this);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void removeScreenAt(int index) {
|
||||
mScreens.get(index).setContainer(null);
|
||||
mScreens.remove(index);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected int getScreenCount() {
|
||||
return mScreens.size();
|
||||
}
|
||||
|
||||
protected Screen getScreenAt(int index) {
|
||||
return mScreens.get(index);
|
||||
}
|
||||
|
||||
private FragmentTransaction getOrCreateTransaction() {
|
||||
if (mCurrentTransaction == null) {
|
||||
mCurrentTransaction = mFragmentManager.beginTransaction();
|
||||
mCurrentTransaction.setReorderingAllowed(true);
|
||||
}
|
||||
return mCurrentTransaction;
|
||||
}
|
||||
|
||||
private void tryCommitTransaction() {
|
||||
if (mCurrentTransaction != null) {
|
||||
mCurrentTransaction.commitNow();
|
||||
mCurrentTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void attachScreen(Screen screen) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
mActiveScreens.add(screen);
|
||||
}
|
||||
|
||||
private void moveToFront(Screen screen) {
|
||||
FragmentTransaction transaction = getOrCreateTransaction();
|
||||
Fragment fragment = screen.getFragment();
|
||||
transaction.remove(fragment);
|
||||
transaction.add(getId(), fragment);
|
||||
}
|
||||
|
||||
private void detachScreen(Screen screen) {
|
||||
getOrCreateTransaction().remove(screen.getFragment());
|
||||
mActiveScreens.remove(screen);
|
||||
}
|
||||
|
||||
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
|
||||
return screen.isActive();
|
||||
}
|
||||
|
||||
private void updateIfNeeded() {
|
||||
if (!mNeedUpdate || mFragmentManager.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
mNeedUpdate = false;
|
||||
|
||||
// detach screens that are no longer active
|
||||
Set<Screen> orphaned = new HashSet<>(mActiveScreens);
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
Screen screen = mScreens.get(i);
|
||||
boolean isActive = isScreenActive(screen, mScreens);
|
||||
if (!isActive && mActiveScreens.contains(screen)) {
|
||||
detachScreen(screen);
|
||||
}
|
||||
orphaned.remove(screen);
|
||||
}
|
||||
if (!orphaned.isEmpty()) {
|
||||
Object[] orphanedAry = orphaned.toArray();
|
||||
for (int i = 0; i < orphanedAry.length; i++) {
|
||||
detachScreen((Screen) orphanedAry[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// attach newly activated screens
|
||||
boolean addedBefore = false;
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
Screen screen = mScreens.get(i);
|
||||
boolean isActive = isScreenActive(screen, mScreens);
|
||||
if (isActive && !mActiveScreens.contains(screen)) {
|
||||
addedBefore = true;
|
||||
attachScreen(screen);
|
||||
} else if (isActive && addedBefore) {
|
||||
moveToFront(screen);
|
||||
}
|
||||
}
|
||||
tryCommitTransaction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
|
||||
@ReactModule(name = ScreenContainerViewManager.REACT_CLASS)
|
||||
public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer> {
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreenContainer";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenContainer createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ScreenContainer(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(ScreenContainer parent, View child, int index) {
|
||||
if (!(child instanceof Screen)) {
|
||||
throw new IllegalArgumentException("Attempt attach child that is not of type RNScreen");
|
||||
}
|
||||
parent.addScreen((Screen) child, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeViewAt(ScreenContainer parent, int index) {
|
||||
parent.removeScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(ScreenContainer parent) {
|
||||
return parent.getScreenCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildAt(ScreenContainer parent, int index) {
|
||||
return parent.getScreenAt(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ScreenStack extends ScreenContainer {
|
||||
|
||||
private float mTransitioning;
|
||||
|
||||
public ScreenStack(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setTransitioning(float transitioning) {
|
||||
if (transitioning != mTransitioning) {
|
||||
mTransitioning = transitioning;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
|
||||
int size = allScreens.size();
|
||||
if (size < 1) {
|
||||
return false;
|
||||
}
|
||||
Screen lastScreen = allScreens.get(size - 1);
|
||||
if (mTransitioning != 0 && size > 1) {
|
||||
Screen secondToLast = allScreens.get(size - 2);
|
||||
return screen == lastScreen || screen == secondToLast;
|
||||
}
|
||||
return screen == lastScreen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
public class ScreenStackViewManager extends ScreenContainerViewManager {
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreenStack";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenContainer createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ScreenStack(reactContext);
|
||||
}
|
||||
|
||||
@ReactProp(name = "transitioning", defaultFloat = 0)
|
||||
public void setTransitioning(ScreenStack view, float transitioning) {
|
||||
view.setTransitioning(transitioning);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
@ReactModule(name = ScreenViewManager.REACT_CLASS)
|
||||
public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreen";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Screen createViewInstance(ThemedReactContext reactContext) {
|
||||
return new Screen(reactContext);
|
||||
}
|
||||
|
||||
@ReactProp(name = "active", defaultBoolean = false)
|
||||
public void setActive(Screen view, boolean active) {
|
||||
view.setActive(active);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user