mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 17:23:42 +08:00
Navigation stack native primitives (#139)
Adds support for stack navigation primitives (UINavigationViewController and Android fragment container with back button support)
This commit is contained in:
committed by
GitHub
parent
4c2aded426
commit
80a466970e
@@ -1,12 +1,12 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@ public class RNScreensPackage implements ReactPackage {
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
new ScreenContainerViewManager(),
|
||||
new ScreenViewManager()
|
||||
new ScreenViewManager(),
|
||||
new ScreenStackViewManager(),
|
||||
new ScreenStackHeaderConfigViewManager(),
|
||||
new ScreenStackHeaderSubviewManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,37 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.PointerEvents;
|
||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
|
||||
public enum StackPresentation {
|
||||
PUSH,
|
||||
MODAL,
|
||||
TRANSPARENT_MODAL
|
||||
}
|
||||
|
||||
public enum StackAnimation {
|
||||
DEFAULT,
|
||||
NONE,
|
||||
FADE
|
||||
}
|
||||
|
||||
public static class ScreenFragment extends Fragment {
|
||||
|
||||
private Screen mScreenView;
|
||||
@@ -36,23 +55,39 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mScreenView.mEventDispatcher.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
private final Fragment mFragment;
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
private @Nullable ScreenContainer mContainer;
|
||||
private boolean mActive;
|
||||
private boolean mTransitioning;
|
||||
private StackPresentation mStackPresentation = StackPresentation.PUSH;
|
||||
private StackAnimation mStackAnimation = StackAnimation.DEFAULT;
|
||||
|
||||
public Screen(Context context) {
|
||||
public Screen(ReactContext context) {
|
||||
super(context);
|
||||
mFragment = new ScreenFragment(this);
|
||||
mEventDispatcher = context.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
protected void onLayout(boolean changed, int l, int t, int b, int r) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
clearDisappearingChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* While transitioning this property allows to optimize rendering behavior on Android and provide
|
||||
* a correct blending options for the animated screen. It is turned on automatically by the container
|
||||
@@ -66,9 +101,20 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
super.setLayerType(transitioning ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOverlappingRendering() {
|
||||
return mTransitioning;
|
||||
public void setStackPresentation(StackPresentation stackPresentation) {
|
||||
mStackPresentation = stackPresentation;
|
||||
}
|
||||
|
||||
public void setStackAnimation(StackAnimation stackAnimation) {
|
||||
mStackAnimation = stackAnimation;
|
||||
}
|
||||
|
||||
public StackAnimation getStackAnimation() {
|
||||
return mStackAnimation;
|
||||
}
|
||||
|
||||
public StackPresentation getStackPresentation() {
|
||||
return mStackPresentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,12 +127,8 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
// ignore - layer type is controlled by `transitioning` prop
|
||||
}
|
||||
|
||||
public void setNeedsOffscreenAlphaCompositing(boolean needsOffscreenAlphaCompositing) {
|
||||
// ignore - offscreen alpha is controlled by `transitioning` prop
|
||||
}
|
||||
|
||||
protected void setContainer(@Nullable ScreenContainer mContainer) {
|
||||
this.mContainer = mContainer;
|
||||
protected void setContainer(@Nullable ScreenContainer container) {
|
||||
mContainer = container;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.modules.core.ChoreographerCompat;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
@@ -21,7 +21,7 @@ import java.util.Set;
|
||||
|
||||
public class ScreenContainer extends ViewGroup {
|
||||
|
||||
private final ArrayList<Screen> mScreens = new ArrayList<>();
|
||||
protected final ArrayList<Screen> mScreens = new ArrayList<>();
|
||||
private final Set<Screen> mActiveScreens = new HashSet<>();
|
||||
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
@@ -79,7 +79,7 @@ public class ScreenContainer extends ViewGroup {
|
||||
return mScreens.get(index);
|
||||
}
|
||||
|
||||
private FragmentActivity findRootFragmentActivity() {
|
||||
protected final FragmentActivity findRootFragmentActivity() {
|
||||
ViewParent parent = this;
|
||||
while (!(parent instanceof ReactRootView) && parent.getParent() != null) {
|
||||
parent = parent.getParent();
|
||||
@@ -105,7 +105,7 @@ public class ScreenContainer extends ViewGroup {
|
||||
return (FragmentActivity) context;
|
||||
}
|
||||
|
||||
private FragmentTransaction getOrCreateTransaction() {
|
||||
protected FragmentTransaction getOrCreateTransaction() {
|
||||
if (mCurrentTransaction == null) {
|
||||
mCurrentTransaction = findRootFragmentActivity().getSupportFragmentManager().beginTransaction();
|
||||
mCurrentTransaction.setReorderingAllowed(true);
|
||||
@@ -113,7 +113,7 @@ public class ScreenContainer extends ViewGroup {
|
||||
return mCurrentTransaction;
|
||||
}
|
||||
|
||||
private void tryCommitTransaction() {
|
||||
protected void tryCommitTransaction() {
|
||||
if (mCurrentTransaction != null) {
|
||||
mCurrentTransaction.commitAllowingStateLoss();
|
||||
mCurrentTransaction = null;
|
||||
@@ -159,7 +159,10 @@ public class ScreenContainer extends ViewGroup {
|
||||
return;
|
||||
}
|
||||
mNeedUpdate = false;
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
protected void onUpdate() {
|
||||
// detach screens that are no longer active
|
||||
Set<Screen> orphaned = new HashSet<>(mActiveScreens);
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
public class ScreenDismissedEvent extends Event<ScreenDismissedEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topDismissed";
|
||||
|
||||
public ScreenDismissedEvent(int viewId) {
|
||||
super(viewId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
|
||||
}
|
||||
}
|
||||
141
android/src/main/java/com/swmansion/rnscreens/ScreenStack.java
Normal file
141
android/src/main/java/com/swmansion/rnscreens/ScreenStack.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.swmansion.rnscreens.Screen.StackAnimation.*;
|
||||
|
||||
public class ScreenStack extends ScreenContainer {
|
||||
|
||||
private final ArrayList<Screen> mStack = new ArrayList<>();
|
||||
private final Set<Screen> mDismissed = new HashSet<>();
|
||||
|
||||
private Screen mTopScreen = null;
|
||||
|
||||
public ScreenStack(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void dismiss(Screen screen) {
|
||||
mDismissed.add(screen);
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
public Screen getTopScreen() {
|
||||
for (int i = getScreenCount() - 1; i >= 0; i--) {
|
||||
Screen screen = getScreenAt(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Stack is empty");
|
||||
}
|
||||
|
||||
public Screen getRootScreen() {
|
||||
for (int i = 0, size = getScreenCount(); i < size; i++) {
|
||||
Screen screen = getScreenAt(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Stack has no root screen set");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScreenAt(int index) {
|
||||
Screen toBeRemoved = getScreenAt(index);
|
||||
mDismissed.remove(toBeRemoved);
|
||||
super.removeScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
// remove all screens previously on stack
|
||||
for (Screen screen : mStack) {
|
||||
if (!mScreens.contains(screen) || mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().remove(screen.getFragment());
|
||||
}
|
||||
}
|
||||
Screen newTop = null;
|
||||
Screen belowTop = null; // this is only set if newTop has TRANSPARENT_MODAL presentation mode
|
||||
|
||||
for (int i = mScreens.size() - 1; i >= 0; i--) {
|
||||
Screen screen = mScreens.get(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
if (newTop == null) {
|
||||
newTop = screen;
|
||||
if (newTop.getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
belowTop = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (Screen screen : mScreens) {
|
||||
// add all new views that weren't on stack before
|
||||
if (!mStack.contains(screen) && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
}
|
||||
// detach all screens that should not be visible
|
||||
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().hide(screen.getFragment());
|
||||
}
|
||||
}
|
||||
// attach "below top" screen if set
|
||||
if (belowTop != null) {
|
||||
final Screen top = newTop;
|
||||
getOrCreateTransaction().show(belowTop.getFragment()).runOnCommit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
top.bringToFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
getOrCreateTransaction().show(newTop.getFragment());
|
||||
|
||||
if (!mStack.contains(newTop)) {
|
||||
// if new top screen wasn't on stack we do "open animation" so long it is not the very first screen on stack
|
||||
if (mTopScreen != null) {
|
||||
// there was some other screen attached before
|
||||
int transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
|
||||
switch (mTopScreen.getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
case FADE:
|
||||
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
|
||||
break;
|
||||
}
|
||||
getOrCreateTransaction().setTransition(transition);
|
||||
}
|
||||
} else if (mTopScreen != null && !mTopScreen.equals(newTop)) {
|
||||
// otherwise if we are performing top screen change we do "back animation"
|
||||
int transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
|
||||
switch (mTopScreen.getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
case FADE:
|
||||
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
|
||||
break;
|
||||
}
|
||||
getOrCreateTransaction().setTransition(transition);
|
||||
}
|
||||
|
||||
mTopScreen = newTop;
|
||||
|
||||
mStack.clear();
|
||||
mStack.addAll(mScreens);
|
||||
|
||||
tryCommitTransaction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.views.text.ReactFontManager;
|
||||
|
||||
public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
|
||||
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
||||
|
||||
private static final class ToolbarWithLayoutLoop extends Toolbar {
|
||||
|
||||
private final Runnable mLayoutRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLayoutEnqueued = false;
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
}
|
||||
};
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
public ToolbarWithLayoutLoop(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
|
||||
if (!mLayoutEnqueued) {
|
||||
mLayoutEnqueued = true;
|
||||
post(mLayoutRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ScreenStackHeaderSubview mConfigSubviews[] = new ScreenStackHeaderSubview[3];
|
||||
private int mSubviewsCount = 0;
|
||||
private String mTitle;
|
||||
private int mTitleColor;
|
||||
private String mTitleFontFamily;
|
||||
private int mTitleFontSize;
|
||||
private int mBackgroundColor;
|
||||
private boolean mIsHidden;
|
||||
private boolean mIsBackButtonHidden;
|
||||
private boolean mIsShadowHidden;
|
||||
private int mTintColor;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private final Toolbar mToolbar;
|
||||
|
||||
private OnBackPressedCallback mBackCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
getScreenStack().dismiss(getScreen());
|
||||
}
|
||||
};
|
||||
private OnClickListener mBackClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getScreenStack().dismiss(getScreen());
|
||||
}
|
||||
};
|
||||
|
||||
public ScreenStackHeaderConfig(Context context) {
|
||||
super(context);
|
||||
setVisibility(View.GONE);
|
||||
|
||||
mToolbar = new ToolbarWithLayoutLoop(context);
|
||||
|
||||
// set primary color as background by default
|
||||
TypedValue tv = new TypedValue();
|
||||
if (context.getTheme().resolveAttribute(android.R.attr.colorPrimary, tv, true)) {
|
||||
mToolbar.setBackgroundColor(tv.data);
|
||||
}
|
||||
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
|
||||
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
|
||||
mHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateToolbarLayout() {
|
||||
mToolbar.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(mWidth, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(mHeight, View.MeasureSpec.EXACTLY));
|
||||
mToolbar.layout(0, 0, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (mWidth != (r - l)) {
|
||||
mWidth = (r - l);
|
||||
updateToolbarLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
update();
|
||||
}
|
||||
|
||||
private Screen getScreen() {
|
||||
ViewParent screen = getParent();
|
||||
if (screen instanceof Screen) {
|
||||
return (Screen) screen;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ScreenStack getScreenStack() {
|
||||
Screen screen = getScreen();
|
||||
if (screen != null) {
|
||||
ScreenContainer container = screen.getContainer();
|
||||
if (container instanceof ScreenStack) {
|
||||
return (ScreenStack) container;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Fragment getScreenFragment() {
|
||||
ViewParent screen = getParent();
|
||||
if (screen instanceof Screen) {
|
||||
return ((Screen) screen).getFragment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void installBackCallback() {
|
||||
mBackCallback.remove();
|
||||
Fragment fragment = getScreenFragment();
|
||||
fragment.requireActivity().getOnBackPressedDispatcher().addCallback(fragment, mBackCallback);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
Screen parent = (Screen) getParent();
|
||||
if (mIsHidden) {
|
||||
if (mToolbar.getParent() != null) {
|
||||
parent.removeView(mToolbar);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mToolbar.getParent() == null) {
|
||||
parent.addView(mToolbar);
|
||||
}
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) parent.getFragment().getActivity();
|
||||
activity.setSupportActionBar(mToolbar);
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
||||
// hide back button
|
||||
final ScreenStack stack = getScreenStack();
|
||||
boolean isRoot = stack == null ? true : stack.getRootScreen() == parent;
|
||||
actionBar.setDisplayHomeAsUpEnabled(isRoot ? false : !mIsBackButtonHidden);
|
||||
if (!isRoot) {
|
||||
installBackCallback();
|
||||
}
|
||||
mBackCallback.setEnabled(!isRoot);
|
||||
|
||||
// when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
|
||||
// navigation click listener. The default behavior set in the wrapper is to call into
|
||||
// menu options handlers, but we prefer the back handling logic to stay here instead.
|
||||
mToolbar.setNavigationOnClickListener(mBackClickListener);
|
||||
|
||||
|
||||
// shadow
|
||||
actionBar.setElevation(mIsShadowHidden ? 0 : TOOLBAR_ELEVATION);
|
||||
|
||||
// title
|
||||
actionBar.setTitle(mTitle);
|
||||
TextView titleTextView = getTitleTextView();
|
||||
if (mTitleColor != 0) {
|
||||
mToolbar.setTitleTextColor(mTitleColor);
|
||||
}
|
||||
if (titleTextView != null) {
|
||||
if (mTitleFontFamily != null) {
|
||||
titleTextView.setTypeface(ReactFontManager.getInstance().getTypeface(
|
||||
mTitleFontFamily, 0, getContext().getAssets()));
|
||||
}
|
||||
if (mTitleFontSize > 0) {
|
||||
titleTextView.setTextSize(mTitleFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
// background
|
||||
if (mBackgroundColor != 0) {
|
||||
mToolbar.setBackgroundColor(mBackgroundColor);
|
||||
}
|
||||
|
||||
// color
|
||||
if (mTintColor != 0) {
|
||||
Drawable navigationIcon = mToolbar.getNavigationIcon();
|
||||
if (navigationIcon != null) {
|
||||
navigationIcon.setColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
}
|
||||
|
||||
// subviews
|
||||
for (int i = 0; i < mSubviewsCount; i++) {
|
||||
ScreenStackHeaderSubview view = mConfigSubviews[i];
|
||||
ScreenStackHeaderSubview.Type type = view.getType();
|
||||
|
||||
Toolbar.LayoutParams params =
|
||||
new Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
|
||||
|
||||
switch (type) {
|
||||
case LEFT:
|
||||
// when there is a left item we need to disable navigation icon
|
||||
// we also hide title as there is no other way to display left side items
|
||||
mToolbar.setNavigationIcon(null);
|
||||
mToolbar.setTitle(null);
|
||||
params.gravity = Gravity.LEFT;
|
||||
break;
|
||||
case RIGHT:
|
||||
params.gravity = Gravity.RIGHT;
|
||||
break;
|
||||
case TITLE:
|
||||
params.width = LayoutParams.MATCH_PARENT;
|
||||
mToolbar.setTitle(null);
|
||||
case CENTER:
|
||||
params.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
break;
|
||||
}
|
||||
|
||||
view.setLayoutParams(params);
|
||||
if (view.getParent() == null) {
|
||||
mToolbar.addView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ScreenStackHeaderSubview getConfigSubview(int index) {
|
||||
return mConfigSubviews[index];
|
||||
}
|
||||
|
||||
public int getConfigSubviewsCount() {
|
||||
return mSubviewsCount;
|
||||
}
|
||||
|
||||
public void removeConfigSubview(int index) {
|
||||
if (mConfigSubviews[index] != null) {
|
||||
mSubviewsCount--;
|
||||
}
|
||||
mConfigSubviews[index] = null;
|
||||
}
|
||||
|
||||
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
|
||||
if (mConfigSubviews[index] == null) {
|
||||
mSubviewsCount++;
|
||||
}
|
||||
mConfigSubviews[index] = child;
|
||||
}
|
||||
|
||||
private TextView getTitleTextView() {
|
||||
for (int i = 0, size = mToolbar.getChildCount(); i < size; i++) {
|
||||
View view = mToolbar.getChildAt(i);
|
||||
if (view instanceof TextView) {
|
||||
TextView tv = (TextView) view;
|
||||
if (tv.getText().equals(mToolbar.getTitle())) {
|
||||
return tv;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
public void setTitleFontFamily(String titleFontFamily) {
|
||||
mTitleFontFamily = titleFontFamily;
|
||||
}
|
||||
|
||||
public void setTitleFontSize(int titleFontSize) {
|
||||
mTitleFontSize = titleFontSize;
|
||||
}
|
||||
|
||||
public void setTitleColor(int color) {
|
||||
mTitleColor = color;
|
||||
}
|
||||
|
||||
public void setTintColor(int color) {
|
||||
mTintColor = color;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int color) {
|
||||
mBackgroundColor = color;
|
||||
}
|
||||
|
||||
public void setHideShadow(boolean hideShadow) {
|
||||
mIsShadowHidden = hideShadow;
|
||||
}
|
||||
|
||||
public void setHideBackButton(boolean hideBackButton) {
|
||||
mIsBackButtonHidden = hideBackButton;
|
||||
}
|
||||
|
||||
public void setHidden(boolean hidden) {
|
||||
mIsHidden = hidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.JSApplicationCausedNativeException;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
@ReactModule(name = ScreenStackHeaderConfigViewManager.REACT_CLASS)
|
||||
public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenStackHeaderConfig> {
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreenStackHeaderConfig";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenStackHeaderConfig createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ScreenStackHeaderConfig(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(ScreenStackHeaderConfig parent, View child, int index) {
|
||||
if (!(child instanceof ScreenStackHeaderSubview)) {
|
||||
throw new JSApplicationCausedNativeException("Config children should be of type " + ScreenStackHeaderSubviewManager.REACT_CLASS);
|
||||
}
|
||||
parent.addConfigSubview((ScreenStackHeaderSubview) child, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeViewAt(ScreenStackHeaderConfig parent, int index) {
|
||||
parent.removeConfigSubview(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(ScreenStackHeaderConfig parent) {
|
||||
return parent.getConfigSubviewsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildAt(ScreenStackHeaderConfig parent, int index) {
|
||||
return parent.getConfigSubview(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ReactProp(name = "title")
|
||||
public void setTitle(ScreenStackHeaderConfig config, String title) {
|
||||
config.setTitle(title);
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleFontFamily")
|
||||
public void setTitleFontFamily(ScreenStackHeaderConfig config, String titleFontFamily) {
|
||||
config.setTitleFontFamily(titleFontFamily);
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleFontSize")
|
||||
public void setTitleFontSize(ScreenStackHeaderConfig config, double titleFontSizeSP) {
|
||||
config.setTitleFontSize((int) PixelUtil.toPixelFromSP(titleFontSizeSP));
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleColor", customType = "Color")
|
||||
public void setTitleColor(ScreenStackHeaderConfig config, int titleColor) {
|
||||
config.setTitleColor(titleColor);
|
||||
}
|
||||
|
||||
@ReactProp(name = "backgroundColor", customType = "Color")
|
||||
public void setBackgroundColor(ScreenStackHeaderConfig config, int titleColor) {
|
||||
config.setBackgroundColor(titleColor);
|
||||
}
|
||||
|
||||
@ReactProp(name = "hideShadow")
|
||||
public void setHideShadow(ScreenStackHeaderConfig config, boolean hideShadow) {
|
||||
config.setHideShadow(hideShadow);
|
||||
}
|
||||
|
||||
@ReactProp(name = "hideBackButton")
|
||||
public void setHideBackButton(ScreenStackHeaderConfig config, boolean hideBackButton) {
|
||||
config.setHideBackButton(hideBackButton);
|
||||
}
|
||||
|
||||
@ReactProp(name = "color", customType = "Color")
|
||||
public void setColor(ScreenStackHeaderConfig config, int color) {
|
||||
config.setTintColor(color);
|
||||
}
|
||||
|
||||
@ReactProp(name = "hidden")
|
||||
public void setHidden(ScreenStackHeaderConfig config, boolean hidden) {
|
||||
config.setHidden(hidden);
|
||||
}
|
||||
|
||||
|
||||
// RCT_EXPORT_VIEW_PROPERTY(backTitle, NSString)
|
||||
// RCT_EXPORT_VIEW_PROPERTY(backTitleFontFamily, NSString)
|
||||
// RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSString)
|
||||
// // `hidden` is an UIView property, we need to use different name internally
|
||||
// RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.views.view.ReactViewGroup;
|
||||
|
||||
public class ScreenStackHeaderSubview extends ReactViewGroup {
|
||||
|
||||
public class Measurements {
|
||||
public int width;
|
||||
public int height;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
LEFT,
|
||||
CENTER,
|
||||
TITLE,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private int mReactWidth, mReactHeight;
|
||||
private final UIManagerModule mUIManager;
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
|
||||
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
|
||||
// dimensions provided by react
|
||||
mReactWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
mReactHeight = MeasureSpec.getSize(heightMeasureSpec);
|
||||
ViewParent parent = getParent();
|
||||
if (parent != null) {
|
||||
forceLayout();
|
||||
((View) parent).requestLayout();
|
||||
}
|
||||
}
|
||||
setMeasuredDimension(mReactWidth, mReactHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
if (changed && (mType == Type.CENTER || mType == Type.TITLE)) {
|
||||
Measurements measurements = new Measurements();
|
||||
measurements.width = right - left;
|
||||
if (mType == Type.CENTER) {
|
||||
// if we want the view to be centered we need to account for the fact that right and left
|
||||
// paddings may not be equal.
|
||||
View parent = (View) getParent();
|
||||
int parentWidth = parent.getWidth();
|
||||
int rightPadding = parentWidth - right;
|
||||
int leftPadding = left;
|
||||
measurements.width = Math.max(0, parentWidth - 2 * Math.max(rightPadding, leftPadding));
|
||||
}
|
||||
measurements.height = bottom - top;
|
||||
mUIManager.setViewLocalData(getId(), measurements);
|
||||
}
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
|
||||
private Type mType = Type.RIGHT;
|
||||
|
||||
public ScreenStackHeaderSubview(ReactContext context) {
|
||||
super(context);
|
||||
mUIManager = context.getNativeModule(UIManagerModule.class);
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return mType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.view.ReactViewGroup;
|
||||
import com.facebook.react.views.view.ReactViewManager;
|
||||
|
||||
@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS)
|
||||
public class ScreenStackHeaderSubviewManager extends ReactViewManager {
|
||||
|
||||
private static class SubviewShadowNode extends LayoutShadowNode {
|
||||
@Override
|
||||
public void setLocalData(Object data) {
|
||||
ScreenStackHeaderSubview.Measurements measurements = (ScreenStackHeaderSubview.Measurements) data;
|
||||
setStyleWidth(measurements.width);
|
||||
setStyleHeight(measurements.height);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreenStackHeaderSubview";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactViewGroup createViewInstance(ThemedReactContext context) {
|
||||
return new ScreenStackHeaderSubview(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutShadowNode createShadowNodeInstance(ReactApplicationContext context) {
|
||||
return new SubviewShadowNode();
|
||||
}
|
||||
|
||||
@ReactProp(name = "type")
|
||||
public void setType(ScreenStackHeaderSubview view, String type) {
|
||||
if ("left".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.LEFT);
|
||||
} else if ("center".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.CENTER);
|
||||
} else if ("title".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.TITLE);
|
||||
} else if ("right".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.RIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
|
||||
@ReactModule(name = ScreenStackViewManager.REACT_CLASS)
|
||||
public class ScreenStackViewManager extends ViewGroupManager<ScreenStack> {
|
||||
|
||||
protected static final String REACT_CLASS = "RNSScreenStack";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenStack createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ScreenStack(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(ScreenStack 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(ScreenStack parent, int index) {
|
||||
prepareOutTransition(parent.getScreenAt(index));
|
||||
parent.removeScreenAt(index);
|
||||
}
|
||||
|
||||
private void prepareOutTransition(Screen screen) {
|
||||
startTransitionRecursive(screen);
|
||||
}
|
||||
|
||||
private void startTransitionRecursive(ViewGroup parent) {
|
||||
for (int i = 0, size = parent.getChildCount(); i < size; i++) {
|
||||
View child = parent.getChildAt(i);
|
||||
parent.startViewTransition(child);
|
||||
if (child instanceof ViewGroup) {
|
||||
startTransitionRecursive((ViewGroup) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(ScreenStack parent) {
|
||||
return parent.getScreenCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildAt(ScreenStack parent, int index) {
|
||||
return parent.getScreenAt(index);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ReactModule(name = ScreenViewManager.REACT_CLASS)
|
||||
public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
|
||||
@@ -24,4 +30,36 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
public void setActive(Screen view, float active) {
|
||||
view.setActive(active != 0);
|
||||
}
|
||||
|
||||
@ReactProp(name = "stackPresentation")
|
||||
public void setStackPresentation(Screen view, String presentation) {
|
||||
if ("push".equals(presentation)) {
|
||||
view.setStackPresentation(Screen.StackPresentation.PUSH);
|
||||
} else if ("modal".equals(presentation)) {
|
||||
view.setStackPresentation(Screen.StackPresentation.MODAL);
|
||||
} else if ("transparentModal".equals(presentation)) {
|
||||
view.setStackPresentation(Screen.StackPresentation.TRANSPARENT_MODAL);
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Unknown presentation type " + presentation);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "stackAnimation")
|
||||
public void setStackAnimation(Screen view, String animation) {
|
||||
if (animation == null || "default".equals(animation)) {
|
||||
view.setStackAnimation(Screen.StackAnimation.DEFAULT);
|
||||
} else if ("none".equals(animation)) {
|
||||
view.setStackAnimation(Screen.StackAnimation.NONE);
|
||||
} else if ("fade".equals(animation)) {
|
||||
view.setStackAnimation(Screen.StackAnimation.FADE);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of(
|
||||
ScreenDismissedEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onDismissed"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user