mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 09:13:43 +08:00
Fix header rendering – layout and transparency (#184)
* Let UINavController control subcontroller view's frames. This PR changes the way we've been handling yoga <> NavController layout interactions. Now we ignore frame updates coming from react for the main Screen view to allow NavController take the controll. In order to keep yoga working we now use `setSize` to pass the dimensions of the view back to yoga such that it can properly calculate layout of the views under Screen component. * Header resizing fixes for Android. In this change we use CoordinatorLayout as a stack screen container to handle rendering of toolbar and screen content. Thanks to this approach we can support collapsable bars in the future. Instead of relying on RN to layout screen container when renered under ScreenStack we rely on Android native layout to measure and position screen content and then use UIManager.setNodeSize to communicate that back to react.
This commit is contained in:
committed by
GitHub
parent
c590283359
commit
4a9a3a877a
@@ -1,10 +1,8 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -13,11 +11,12 @@ import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.GuardedRunnable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.MeasureSpecAssertions;
|
||||
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 {
|
||||
|
||||
@@ -33,34 +32,6 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
FADE
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mScreenView.mEventDispatcher.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
private static OnAttachStateChangeListener sShowSoftKeyboardOnAttach = new OnAttachStateChangeListener() {
|
||||
|
||||
@Override
|
||||
@@ -77,8 +48,7 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
}
|
||||
};
|
||||
|
||||
private final Fragment mFragment;
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
private @Nullable Fragment mFragment;
|
||||
private @Nullable ScreenContainer mContainer;
|
||||
private boolean mActive;
|
||||
private boolean mTransitioning;
|
||||
@@ -87,13 +57,23 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
|
||||
public Screen(ReactContext context) {
|
||||
super(context);
|
||||
mFragment = new ScreenFragment(this);
|
||||
mEventDispatcher = context.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int b, int r) {
|
||||
// no-op
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (changed) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
final ReactContext reactContext = (ReactContext) getContext();
|
||||
reactContext.runOnNativeModulesQueueThread(
|
||||
new GuardedRunnable(reactContext) {
|
||||
@Override
|
||||
public void runGuarded() {
|
||||
reactContext.getNativeModule(UIManagerModule.class)
|
||||
.updateNodeSize(getId(), width, height);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,14 +146,18 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
mContainer = container;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
return mContainer;
|
||||
protected void setFragment(Fragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
protected Fragment getFragment() {
|
||||
protected @Nullable Fragment getFragment() {
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
return mContainer;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
if (active == mActive) {
|
||||
return;
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
|
||||
@@ -16,13 +15,12 @@ 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 {
|
||||
public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
protected final ArrayList<Screen> mScreens = new ArrayList<>();
|
||||
private final Set<Screen> mActiveScreens = new HashSet<>();
|
||||
protected final ArrayList<T> mScreenFragments = new ArrayList<>();
|
||||
private final Set<ScreenFragment> mActiveScreenFragments = new HashSet<>();
|
||||
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
@@ -59,24 +57,30 @@ public class ScreenContainer extends ViewGroup {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected T adapt(Screen screen) {
|
||||
return (T) new ScreenFragment(screen);
|
||||
}
|
||||
|
||||
protected void addScreen(Screen screen, int index) {
|
||||
mScreens.add(index, screen);
|
||||
T fragment = adapt(screen);
|
||||
screen.setFragment(fragment);
|
||||
mScreenFragments.add(index, fragment);
|
||||
screen.setContainer(this);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void removeScreenAt(int index) {
|
||||
mScreens.get(index).setContainer(null);
|
||||
mScreens.remove(index);
|
||||
mScreenFragments.get(index).getScreen().setContainer(null);
|
||||
mScreenFragments.remove(index);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected int getScreenCount() {
|
||||
return mScreens.size();
|
||||
return mScreenFragments.size();
|
||||
}
|
||||
|
||||
protected Screen getScreenAt(int index) {
|
||||
return mScreens.get(index);
|
||||
return mScreenFragments.get(index).getScreen();
|
||||
}
|
||||
|
||||
protected final FragmentActivity findRootFragmentActivity() {
|
||||
@@ -120,25 +124,24 @@ public class ScreenContainer extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void attachScreen(Screen screen) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
mActiveScreens.add(screen);
|
||||
private void attachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().add(getId(), screenFragment);
|
||||
mActiveScreenFragments.add(screenFragment);
|
||||
}
|
||||
|
||||
private void moveToFront(Screen screen) {
|
||||
private void moveToFront(ScreenFragment screenFragment) {
|
||||
FragmentTransaction transaction = getOrCreateTransaction();
|
||||
Fragment fragment = screen.getFragment();
|
||||
transaction.remove(fragment);
|
||||
transaction.add(getId(), fragment);
|
||||
transaction.remove(screenFragment);
|
||||
transaction.add(getId(), screenFragment);
|
||||
}
|
||||
|
||||
private void detachScreen(Screen screen) {
|
||||
getOrCreateTransaction().remove(screen.getFragment());
|
||||
mActiveScreens.remove(screen);
|
||||
private void detachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().remove(screenFragment);
|
||||
mActiveScreenFragments.remove(screenFragment);
|
||||
}
|
||||
|
||||
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
|
||||
return screen.isActive();
|
||||
protected boolean isScreenActive(ScreenFragment screenFragment) {
|
||||
return screenFragment.getScreen().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,26 +167,26 @@ public class ScreenContainer extends ViewGroup {
|
||||
|
||||
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++) {
|
||||
Screen screen = mScreens.get(i);
|
||||
boolean isActive = isScreenActive(screen, mScreens);
|
||||
if (!isActive && mActiveScreens.contains(screen)) {
|
||||
detachScreen(screen);
|
||||
Set<ScreenFragment> orphaned = new HashSet<>(mActiveScreenFragments);
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (!isActive && mActiveScreenFragments.contains(screenFragment)) {
|
||||
detachScreen(screenFragment);
|
||||
}
|
||||
orphaned.remove(screen);
|
||||
orphaned.remove(screenFragment);
|
||||
}
|
||||
if (!orphaned.isEmpty()) {
|
||||
Object[] orphanedAry = orphaned.toArray();
|
||||
for (int i = 0; i < orphanedAry.length; i++) {
|
||||
detachScreen((Screen) orphanedAry[i]);
|
||||
detachScreen((ScreenFragment) orphanedAry[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// detect if we are "transitioning" based on the number of active screens
|
||||
int activeScreens = 0;
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
if (isScreenActive(mScreens.get(i), mScreens)) {
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
if (isScreenActive(mScreenFragments.get(i))) {
|
||||
activeScreens += 1;
|
||||
}
|
||||
}
|
||||
@@ -191,16 +194,16 @@ public class ScreenContainer extends ViewGroup {
|
||||
|
||||
// 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)) {
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (isActive && !mActiveScreenFragments.contains(screenFragment)) {
|
||||
addedBefore = true;
|
||||
attachScreen(screen);
|
||||
attachScreen(screenFragment);
|
||||
} else if (isActive && addedBefore) {
|
||||
moveToFront(screen);
|
||||
moveToFront(screenFragment);
|
||||
}
|
||||
screen.setTransitioning(transitioning);
|
||||
screenFragment.getScreen().setTransitioning(transitioning);
|
||||
}
|
||||
tryCommitTransaction();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
public class ScreenFragment extends Fragment {
|
||||
|
||||
protected 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;
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
((ReactContext) mScreenView.getContext())
|
||||
.getNativeModule(UIManagerModule.class)
|
||||
.getEventDispatcher()
|
||||
.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
@@ -8,42 +8,76 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScreenStack extends ScreenContainer {
|
||||
public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
|
||||
private final ArrayList<Screen> mStack = new ArrayList<>();
|
||||
private final Set<Screen> mDismissed = new HashSet<>();
|
||||
private final ArrayList<ScreenStackFragment> mStack = new ArrayList<>();
|
||||
private final Set<ScreenStackFragment> mDismissed = new HashSet<>();
|
||||
|
||||
private Screen mTopScreen = null;
|
||||
private ScreenStackFragment mTopScreen = null;
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
public ScreenStack(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void dismiss(Screen screen) {
|
||||
mDismissed.add(screen);
|
||||
public void dismiss(ScreenStackFragment screenFragment) {
|
||||
mDismissed.add(screenFragment);
|
||||
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)) {
|
||||
if (!mDismissed.contains(screen.getFragment())) {
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Stack has no root screen set");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenStackFragment adapt(Screen screen) {
|
||||
return new ScreenStackFragment(screen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
for (int i = 0, size = getChildCount(); i < size; i++) {
|
||||
getChildAt(i).layout(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
for (int i = 0, size = getChildCount(); i < size; i++) {
|
||||
getChildAt(i).measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
|
||||
if (!mLayoutEnqueued) {
|
||||
mLayoutEnqueued = true;
|
||||
post(mLayoutRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScreenAt(int index) {
|
||||
Screen toBeRemoved = getScreenAt(index);
|
||||
@@ -54,20 +88,20 @@ public class ScreenStack extends ScreenContainer {
|
||||
@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());
|
||||
for (ScreenStackFragment screen : mStack) {
|
||||
if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().remove(screen);
|
||||
}
|
||||
}
|
||||
Screen newTop = null;
|
||||
Screen belowTop = null; // this is only set if newTop has TRANSPARENT_MODAL presentation mode
|
||||
ScreenStackFragment newTop = null;
|
||||
ScreenStackFragment 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);
|
||||
for (int i = mScreenFragments.size() - 1; i >= 0; i--) {
|
||||
ScreenStackFragment screen = mScreenFragments.get(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
if (newTop == null) {
|
||||
newTop = screen;
|
||||
if (newTop.getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
|
||||
if (newTop.getScreen().getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -78,34 +112,34 @@ public class ScreenStack extends ScreenContainer {
|
||||
}
|
||||
|
||||
|
||||
for (Screen screen : mScreens) {
|
||||
for (ScreenStackFragment screen : mScreenFragments) {
|
||||
// add all new views that weren't on stack before
|
||||
if (!mStack.contains(screen) && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
getOrCreateTransaction().add(getId(), screen);
|
||||
}
|
||||
// detach all screens that should not be visible
|
||||
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().hide(screen.getFragment());
|
||||
getOrCreateTransaction().hide(screen);
|
||||
}
|
||||
}
|
||||
// attach "below top" screen if set
|
||||
if (belowTop != null) {
|
||||
final Screen top = newTop;
|
||||
getOrCreateTransaction().show(belowTop.getFragment()).runOnCommit(new Runnable() {
|
||||
final ScreenStackFragment top = newTop;
|
||||
getOrCreateTransaction().show(belowTop).runOnCommit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
top.bringToFront();
|
||||
top.getScreen().bringToFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
getOrCreateTransaction().show(newTop.getFragment());
|
||||
getOrCreateTransaction().show(newTop);
|
||||
|
||||
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()) {
|
||||
switch (mTopScreen.getScreen().getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
@@ -118,7 +152,7 @@ public class ScreenStack extends ScreenContainer {
|
||||
} 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()) {
|
||||
switch (mTopScreen.getScreen().getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
@@ -132,7 +166,7 @@ public class ScreenStack extends ScreenContainer {
|
||||
mTopScreen = newTop;
|
||||
|
||||
mStack.clear();
|
||||
mStack.addAll(mScreens);
|
||||
mStack.addAll(mScreenFragments);
|
||||
|
||||
tryCommitTransaction();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
|
||||
public class ScreenStackFragment extends ScreenFragment {
|
||||
|
||||
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
||||
|
||||
private AppBarLayout mAppBarLayout;
|
||||
private Toolbar mToolbar;
|
||||
private boolean mShadowHidden;
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
CoordinatorLayout view = new CoordinatorLayout(getContext());
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
@@ -16,42 +17,10 @@ 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;
|
||||
@@ -63,20 +32,18 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
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());
|
||||
getScreenStack().dismiss(getScreenFragment());
|
||||
}
|
||||
};
|
||||
private OnClickListener mBackClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getScreenStack().dismiss(getScreen());
|
||||
getScreenStack().dismiss(getScreenFragment());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,36 +51,18 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
super(context);
|
||||
setVisibility(View.GONE);
|
||||
|
||||
mToolbar = new ToolbarWithLayoutLoop(context);
|
||||
mToolbar = new Toolbar(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();
|
||||
}
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,10 +90,13 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Fragment getScreenFragment() {
|
||||
private ScreenStackFragment getScreenFragment() {
|
||||
ViewParent screen = getParent();
|
||||
if (screen instanceof Screen) {
|
||||
return ((Screen) screen).getFragment();
|
||||
Fragment fragment = ((Screen) screen).getFragment();
|
||||
if (fragment instanceof ScreenStackFragment) {
|
||||
return (ScreenStackFragment) fragment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -159,16 +111,16 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
Screen parent = (Screen) getParent();
|
||||
if (mIsHidden) {
|
||||
if (mToolbar.getParent() != null) {
|
||||
parent.removeView(mToolbar);
|
||||
getScreenFragment().removeToolbar();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mToolbar.getParent() == null) {
|
||||
parent.addView(mToolbar);
|
||||
getScreenFragment().setToolbar(mToolbar);
|
||||
}
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) parent.getFragment().getActivity();
|
||||
AppCompatActivity activity = (AppCompatActivity) getScreenFragment().getActivity();
|
||||
activity.setSupportActionBar(mToolbar);
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
||||
@@ -188,7 +140,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
|
||||
|
||||
// shadow
|
||||
actionBar.setElevation(mIsShadowHidden ? 0 : TOOLBAR_ELEVATION);
|
||||
getScreenFragment().setToolbarShadowHidden(mIsShadowHidden);
|
||||
|
||||
// title
|
||||
actionBar.setTitle(mTitle);
|
||||
|
||||
@@ -59,4 +59,9 @@ public class ScreenStackViewManager extends ViewGroupManager<ScreenStack> {
|
||||
public View getChildAt(ScreenStack parent, int index) {
|
||||
return parent.getScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user