Skip to content

Commit

Permalink
Fix header rendering – layout and transparency (#184)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
kmagiera committed Oct 18, 2019
1 parent c590283 commit 4a9a3a8
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 217 deletions.
2 changes: 1 addition & 1 deletion Example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
Expand Down
4 changes: 3 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ repositories {

dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
}

def configureReactNativePom(def pom) {
Expand Down
66 changes: 25 additions & 41 deletions android/src/main/java/com/swmansion/rnscreens/Screen.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,11 +11,12 @@
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 {

Expand All @@ -33,34 +32,6 @@ public enum StackAnimation {
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
Expand All @@ -77,8 +48,7 @@ public void onViewDetachedFromWindow(View view) {
}
};

private final Fragment mFragment;
private final EventDispatcher mEventDispatcher;
private @Nullable Fragment mFragment;
private @Nullable ScreenContainer mContainer;
private boolean mActive;
private boolean mTransitioning;
Expand All @@ -87,13 +57,23 @@ public void onViewDetachedFromWindow(View view) {

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
Expand Down Expand Up @@ -166,14 +146,18 @@ protected void setContainer(@Nullable ScreenContainer container) {
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;
Expand Down
81 changes: 42 additions & 39 deletions android/src/main/java/com/swmansion/rnscreens/ScreenContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import android.view.ViewParent;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;

Expand All @@ -16,13 +15,12 @@

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;
Expand Down Expand Up @@ -59,24 +57,30 @@ protected void notifyChildUpdate() {
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() {
Expand Down Expand Up @@ -120,25 +124,24 @@ protected void tryCommitTransaction() {
}
}

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
Expand All @@ -164,43 +167,43 @@ private void updateIfNeeded() {

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;
}
}
boolean transitioning = activeScreens > 1;

// 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();
}
Expand Down
50 changes: 50 additions & 0 deletions android/src/main/java/com/swmansion/rnscreens/ScreenFragment.java
Original file line number Diff line number Diff line change
@@ -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()));
}
}

0 comments on commit 4a9a3a8

Please sign in to comment.