Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca84419e0c | |||
| a04dec7ec1 | |||
| 81a499d121 | |||
| ff8ab621bc | |||
| cdb5e5a978 | |||
| effa410eae | |||
| df27bfaa3d | |||
| a865c210b6 | |||
| 7820748cce |
@@ -20,26 +20,26 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
compile 'com.bluelinelabs:conductor:2.1.1'
|
||||
compile 'com.bluelinelabs:conductor:2.1.3'
|
||||
|
||||
// If you want the components that go along with
|
||||
// Android's support libraries (currently just a PagerAdapter):
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.2'
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.3'
|
||||
|
||||
// If you want RxJava lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.2'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.3'
|
||||
|
||||
// If you want RxJava2 lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.2'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.3'
|
||||
```
|
||||
|
||||
SNAPSHOT:
|
||||
|
||||
```gradle
|
||||
compile 'com.bluelinelabs:conductor:2.1.3-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.3-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.3-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.3-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor:2.1.4-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.4-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.4-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.4-SNAPSHOT'
|
||||
```
|
||||
|
||||
You also have to add the url to the snapshot repository:
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.IntentSender;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -86,6 +87,7 @@ public abstract class Controller {
|
||||
private final List<LifecycleListener> lifecycleListeners = new ArrayList<>();
|
||||
private final ArrayList<String> requestedPermissions = new ArrayList<>();
|
||||
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
|
||||
private final Handler handler = new Handler();
|
||||
private WeakReference<View> destroyedView;
|
||||
private boolean isPerformingExitTransition;
|
||||
|
||||
@@ -603,7 +605,7 @@ public abstract class Controller {
|
||||
*
|
||||
* @param lifecycleListener The listener
|
||||
*/
|
||||
public void addLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
|
||||
public final void addLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
|
||||
if (!lifecycleListeners.contains(lifecycleListener)) {
|
||||
lifecycleListeners.add(lifecycleListener);
|
||||
}
|
||||
@@ -614,7 +616,7 @@ public abstract class Controller {
|
||||
*
|
||||
* @param lifecycleListener The listener to be removed
|
||||
*/
|
||||
public void removeLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
|
||||
public final void removeLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
|
||||
lifecycleListeners.remove(lifecycleListener);
|
||||
}
|
||||
|
||||
@@ -817,7 +819,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
private void attach(@NonNull View view) {
|
||||
private void attach(@NonNull final View view) {
|
||||
attachedToUnownedParent = router == null || view.getParent() != router.container;
|
||||
if (attachedToUnownedParent) {
|
||||
return;
|
||||
@@ -833,16 +835,23 @@ public abstract class Controller {
|
||||
attached = true;
|
||||
needsAttach = false;
|
||||
|
||||
onAttach(view);
|
||||
// This must be posted in a handler to ensure the system can finish attaching views if needed. Otherwise
|
||||
// we can get NPEs when developers decide to immediately pop this controller from onAttach.
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onAttach(view);
|
||||
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postAttach(this, view);
|
||||
}
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postAttach(Controller.this, view);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
|
||||
|
||||
@@ -125,26 +125,37 @@ public abstract class Router {
|
||||
public boolean popController(@NonNull Controller controller) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
RouterTransaction topController = backstack.peek();
|
||||
boolean poppingTopController = topController != null && topController.controller == controller;
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
boolean poppingTopController = topTransaction != null && topTransaction.controller == controller;
|
||||
|
||||
if (poppingTopController) {
|
||||
trackDestroyingController(backstack.pop());
|
||||
performControllerChange(backstack.peek(), topTransaction, false);
|
||||
} else {
|
||||
RouterTransaction removedTransaction = null;
|
||||
RouterTransaction nextTransaction = null;
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller == controller) {
|
||||
if (controller.isAttached()) {
|
||||
trackDestroyingController(transaction);
|
||||
}
|
||||
backstack.remove(transaction);
|
||||
removedTransaction = transaction;
|
||||
} else if (removedTransaction != null) {
|
||||
if (!transaction.controller.isAttached()) {
|
||||
nextTransaction = transaction;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (poppingTopController) {
|
||||
performControllerChange(backstack.peek(), topController, false);
|
||||
if (removedTransaction != null) {
|
||||
performControllerChange(nextTransaction, removedTransaction, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (popsLastView) {
|
||||
return topController != null;
|
||||
return topTransaction != null;
|
||||
} else {
|
||||
return !backstack.isEmpty();
|
||||
}
|
||||
|
||||
+18
-1
@@ -122,13 +122,20 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
if (to.getWidth() <= 0 && to.getHeight() <= 0) {
|
||||
readyToAnimate = false;
|
||||
to.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
||||
boolean hasRun;
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
final ViewTreeObserver observer = to.getViewTreeObserver();
|
||||
if (observer.isAlive()) {
|
||||
observer.removeOnPreDrawListener(this);
|
||||
}
|
||||
performAnimation(container, from, to, isPush, addingToView, changeListener);
|
||||
|
||||
// Apparently this gets called multiple times, even if removeOnPreDrawListener is called successfully.
|
||||
if (!hasRun) {
|
||||
hasRun = true;
|
||||
performAnimation(container, from, to, isPush, addingToView, changeListener);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -160,6 +167,16 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
complete(changeListener, null);
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
if (from != null && (!isPush || removesFromViewOnPush)) {
|
||||
container.removeView(from);
|
||||
}
|
||||
complete(changeListener, null);
|
||||
if (isPush && from != null) {
|
||||
resetFromView(from);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
animator = getAnimator(container, from, to, isPush, toAddedToContainer);
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class HorizontalChangeHandler extends AnimatorChangeHandler {
|
||||
}
|
||||
if (to != null) {
|
||||
// Allow this to have a nice transition when coming off an aborted push animation
|
||||
float fromLeft = from != null ? from.getX() : 0;
|
||||
float fromLeft = from != null ? from.getTranslationX() : 0;
|
||||
animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, fromLeft - to.getWidth(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
+16
-3
@@ -20,10 +20,11 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
public interface OnTransitionPreparedListener {
|
||||
public void onPrepared();
|
||||
void onPrepared();
|
||||
}
|
||||
|
||||
private boolean canceled;
|
||||
private boolean needsImmediateCompletion;
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Transition to use while replacing Views.
|
||||
@@ -43,12 +44,24 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
super.completeImmediately();
|
||||
|
||||
needsImmediateCompletion = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
if (canceled) {
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
executePropertyChanges(container, from, to, null, isPush);
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
final Transition transition = getTransition(container, from, to, isPush);
|
||||
transition.addListener(new TransitionListener() {
|
||||
@@ -109,10 +122,10 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called. This will be null only if another ControllerChangeHandler immediately overrides this one.
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) {
|
||||
container.removeView(from);
|
||||
}
|
||||
|
||||
+16
@@ -7,6 +7,7 @@ import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
@@ -78,4 +79,19 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
changeHandler.onAbortPush(newHandler, newTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
changeHandler.completeImmediately();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceRemoveViewOnPush(boolean force) {
|
||||
changeHandler.setForceRemoveViewOnPush(force);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
}
|
||||
|
||||
private boolean rootAttached = false;
|
||||
boolean childrenAttached = false;
|
||||
private boolean childrenAttached = false;
|
||||
private boolean activityStopped = false;
|
||||
private ReportedState reportedState = ReportedState.VIEW_DETACHED;
|
||||
private ViewAttachListener attachListener;
|
||||
@@ -55,7 +55,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
rootAttached = false;
|
||||
if (childrenAttached) {
|
||||
childrenAttached = false;
|
||||
reportDetached();
|
||||
reportDetached(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,34 +78,33 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
|
||||
public void onActivityStopped() {
|
||||
activityStopped = true;
|
||||
reportDetached();
|
||||
reportDetached(true);
|
||||
}
|
||||
|
||||
void reportAttached() {
|
||||
private void reportAttached() {
|
||||
if (rootAttached && childrenAttached && !activityStopped && reportedState != ReportedState.ATTACHED) {
|
||||
reportedState = ReportedState.ATTACHED;
|
||||
attachListener.onAttached();
|
||||
}
|
||||
}
|
||||
|
||||
void reportDetached() {
|
||||
private void reportDetached(boolean detachedForActivity) {
|
||||
boolean wasDetachedForActivity = reportedState == ReportedState.ACTIVITY_STOPPED;
|
||||
boolean isDetachedForActivity = rootAttached;
|
||||
|
||||
if (isDetachedForActivity) {
|
||||
if (detachedForActivity) {
|
||||
reportedState = ReportedState.ACTIVITY_STOPPED;
|
||||
} else {
|
||||
reportedState = ReportedState.VIEW_DETACHED;
|
||||
}
|
||||
|
||||
if (wasDetachedForActivity && !isDetachedForActivity) {
|
||||
if (wasDetachedForActivity && !detachedForActivity) {
|
||||
attachListener.onViewDetachAfterStop();
|
||||
} else {
|
||||
attachListener.onDetached(isDetachedForActivity);
|
||||
attachListener.onDetached(detachedForActivity);
|
||||
}
|
||||
}
|
||||
|
||||
void listenForDeepestChildAttach(final View view, final ChildAttachListener attachListener) {
|
||||
private void listenForDeepestChildAttach(final View view, final ChildAttachListener attachListener) {
|
||||
if (!(view instanceof ViewGroup)) {
|
||||
attachListener.onAttached();
|
||||
return;
|
||||
|
||||
@@ -86,18 +86,6 @@ public class ViewLeakTests {
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStopWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStopWhenPushNeverCompleted() {
|
||||
Controller controller = new TestController();
|
||||
@@ -110,9 +98,21 @@ public class ViewLeakTests {
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityDestroyWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true).destroy();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
public static class NeverAddChangeHandler extends ControllerChangeHandler {
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable final View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
if (from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
|
||||
+15
-6
@@ -15,7 +15,6 @@ import com.bluelinelabs.conductor.changehandler.TransitionChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.transitions.FabTransform;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils.TransitionEndListener;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler {
|
||||
@@ -64,7 +63,7 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (isPush) {
|
||||
fabParent.removeView(fab);
|
||||
container.addView(to);
|
||||
@@ -74,7 +73,7 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
* Because otherwise we will be lost when trying to transition back.
|
||||
* Set it to invisible because we don't want it to jump back after the transition
|
||||
*/
|
||||
transition.addListener(new AnimUtils.TransitionEndListener() {
|
||||
AnimUtils.TransitionEndListener endListener = new AnimUtils.TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fab.setVisibility(View.GONE);
|
||||
@@ -82,19 +81,29 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
fab = null;
|
||||
fabParent = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
if (transition != null) {
|
||||
transition.addListener(endListener);
|
||||
} else {
|
||||
endListener.onTransitionCompleted(null);
|
||||
}
|
||||
} else {
|
||||
dialogBackground.setVisibility(View.INVISIBLE);
|
||||
fabParent.addView(fab);
|
||||
container.removeView(from);
|
||||
|
||||
transition.addListener(new TransitionEndListener() {
|
||||
AnimUtils.TransitionEndListener endListener = new AnimUtils.TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fabParent.removeView(dialogBackground);
|
||||
dialogBackground = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
if (transition != null) {
|
||||
transition.addListener(endListener);
|
||||
} else {
|
||||
endListener.onTransitionCompleted(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class SharedElementDelayingChangeHandler extends ArcFadeMoveChangeHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (to != null) {
|
||||
to.setVisibility(View.VISIBLE);
|
||||
|
||||
|
||||
+10
-7
@@ -49,6 +49,12 @@ public class PagerController extends BaseController {
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_pager, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
@@ -58,16 +64,13 @@ public class PagerController extends BaseController {
|
||||
|
||||
@Override
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
viewPager.setAdapter(null);
|
||||
if (!getActivity().isChangingConfigurations()) {
|
||||
viewPager.setAdapter(null);
|
||||
}
|
||||
tabLayout.setupWithViewPager(null);
|
||||
super.onDestroyView(view);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_pager, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "ViewPager Demo";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VERSION_NAME=2.1.3-SNAPSHOT
|
||||
VERSION_NAME=2.1.4-SNAPSHOT
|
||||
VERSION_CODE=2
|
||||
GROUP=com.bluelinelabs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user