Compare commits

...

9 Commits

13 changed files with 145 additions and 68 deletions
+8 -8
View File
@@ -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();
}
@@ -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);
@@ -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));
}
}
@@ -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);
}
@@ -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,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);
}
}
}
@@ -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);
@@ -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
View File
@@ -1,4 +1,4 @@
VERSION_NAME=2.1.3-SNAPSHOT
VERSION_NAME=2.1.4-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs