Compare commits

...

16 Commits

Author SHA1 Message Date
Eric Kuck e028ed42da Version bump 2016-04-16 14:22:38 -05:00
Eric Kuck 43dc561ac2 Fixed up some save/restore view state stuff (incl. tests) 2016-04-16 13:51:37 -05:00
Eric Kuck 217b55090a Added test for backgrounding the host activity 2016-04-16 13:18:24 -05:00
Eric Kuck 5c8b78e41d Fixed orientation change test 2016-04-16 12:25:45 -05:00
Eric Kuck 899dd70d50 Merge pull request #25 from sockeqwe/viewStateListener
added methods to lifecyclelistener for saving/restoring viewstate
2016-04-16 12:04:51 -05:00
Eric Kuck d945571d31 Improved controller lifecycle test coverage (and clarity) 2016-04-16 12:04:20 -05:00
Hannes Dorfmann b7a4386d22 Renamed parameter of onRestoreViewState() to savedViewState 2016-04-16 14:01:41 +02:00
Hannes Dorfmann b117307340 added methods to lifecyclelistener for saving/restoring viewstate 2016-04-15 19:52:43 +02:00
Eric Kuck b8ccf3623f Fixed onRestoreState call ordering for controllers with parent/child relationships 2016-04-14 14:20:56 -05:00
Eric Kuck 495145b72b Router, Activity, target controller, etc is all now available in onRestoreInstanceState 2016-04-13 15:39:09 -05:00
Eric Kuck 99e25d65f2 Merge pull request #20 from adi1133/develop
Fix crash caused by recreating static nested classes using reflection
2016-04-12 16:41:05 -05:00
Adi Pascu 2619d13c8d Fix crash caused by recreating static nested classes using reflection 2016-04-12 23:49:10 +03:00
Eric Kuck 62a5a81107 Fixed typo 2016-04-12 14:33:03 -05:00
Eric Kuck d234dd4c75 Added external onActivityResult method to Controllers (for use with things like the Facebook SDK)
Removed CircularRevealChangeHandler from the core project
2016-04-12 14:18:01 -05:00
Eric Kuck a48b49cdbe All demos should now run on API < 21 2016-04-06 12:07:51 -05:00
Eric Kuck f0a488c711 Added options menu support for controllers
Updated demo app to have a toolbar
2016-04-05 13:34:14 -05:00
43 changed files with 1156 additions and 236 deletions
+3 -3
View File
@@ -20,14 +20,14 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
compile 'com.bluelinelabs:conductor:1.1.0'
compile 'com.bluelinelabs:conductor:1.1.4'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:1.1.0'
compile 'com.bluelinelabs:conductor-support:1.1.4'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.0'
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.4'
```
## Components to Know
+1 -1
View File
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
}
}
@@ -11,6 +11,9 @@ import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
@@ -18,6 +21,7 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.internal.ClassUtils;
import com.bluelinelabs.conductor.internal.RouterRequiringFunc;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -50,9 +54,12 @@ public abstract class Controller {
private final Bundle mArgs;
private Bundle mViewState;
private Bundle mSavedInstanceState;
private boolean mIsBeingDestroyed;
private boolean mDestroyed;
private boolean mAttached;
private boolean mHasOptionsMenu;
private boolean mOptionsMenuHidden;
private boolean mViewIsAttached;
private Router mRouter;
private View mView;
@@ -60,12 +67,14 @@ public abstract class Controller {
private String mInstanceId;
private String mTargetInstanceId;
private boolean mNeedsAttach;
private boolean mHasSavedViewState;
private ControllerChangeHandler mOverriddenPushHandler;
private ControllerChangeHandler mOverriddenPopHandler;
private RetainViewMode mRetainViewMode = RetainViewMode.RELEASE_DETACH;
private final List<ChildControllerTransaction> mChildControllers = new ArrayList<>();
private final List<LifecycleListener> mLifecycleListeners = new ArrayList<>();
private final ArrayList<String> mRequestedPermissions = new ArrayList<>();
private final ArrayList<RouterRequiringFunc> mOnRouterSetListeners = new ArrayList<>();
static Controller newInstance(Bundle bundle) {
final String className = bundle.getString(KEY_CLASS_NAME);
@@ -112,7 +121,7 @@ public abstract class Controller {
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}, plus
* any binding code.
*
* @param inflater The LayoutInflater that should be used to inflate views
* @param inflater The LayoutInflater that should be used to inflate views
* @param container The parent view that this Controller's view will eventually be attached to.
* This Controller's view should NOT be added in this method. It is simply passed in
* so that valid LayoutParams can be used during inflation.
@@ -307,7 +316,7 @@ public abstract class Controller {
* Called when this Controller begins the process of being swapped in or out of the host view.
*
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
* @param changeType The type of change that's occurring
* @param changeType The type of change that's occurring
*/
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
@@ -315,7 +324,7 @@ public abstract class Controller {
* Called when this Controller completes the process of being swapped in or out of the host view.
*
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
* @param changeType The type of change that occurred
* @param changeType The type of change that occurred
*/
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
@@ -363,7 +372,7 @@ public abstract class Controller {
* Controller lifecycle (ex: when another Controller has been pushed on top of it), care should be taken
* to save anything needed to reconstruct the View.
*
* @param view This Controller's View, passed for convenience
* @param view This Controller's View, passed for convenience
* @param outState The Bundle into which the View state should be saved
*/
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { }
@@ -372,7 +381,7 @@ public abstract class Controller {
* Restores data that was saved in the {@link #onSaveViewState(View, Bundle)} method. This should be overridden
* to restore the View's state to where it was before it was destroyed.
*
* @param view This Controller's View, passed for convenience
* @param view This Controller's View, passed for convenience
* @param savedViewState The bundle that has data to be restored
*/
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) { }
@@ -392,18 +401,43 @@ public abstract class Controller {
*/
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { }
/**
* Calls startActivity(Intent) from this Controller's host Activity.
*/
public final void startActivity(final Intent intent) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivity(intent); }
});
}
/**
* Calls startActivityForResult(Intent, int) from this Controller's host Activity.
*/
public final void startActivityForResult(Intent intent, int requestCode) {
getRouter().getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode);
public final void startActivityForResult(final Intent intent, final int requestCode) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode); }
});
}
/**
* Calls startActivityForResult(Intent, int, Bundle) from this Controller's host Activity.
*/
public final void startActivityForResult(Intent intent, int requestCode, Bundle options) {
getRouter().getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode, options);
public final void startActivityForResult(final Intent intent, final int requestCode, final Bundle options) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().startActivityForResult(mInstanceId, intent, requestCode, options); }
});
}
/**
* Registers this Controller to handle onActivityResult responses. Calling this method is NOT
* necessary when calling {@link #startActivityForResult(Intent, int)}
*
* @param requestCode The request code being registered for.
*/
public final void registerForActivityResult(final int requestCode) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().registerForActivityRequest(mInstanceId, requestCode); }
});
}
/**
@@ -411,8 +445,8 @@ public abstract class Controller {
* the result.
*
* @param requestCode The requestCode passed to startActivityForResult
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
* @param data The data Intent that was returned to the host Activity's onActivityResult method
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
* @param data The data Intent that was returned to the host Activity's onActivityResult method
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) { }
@@ -422,9 +456,12 @@ public abstract class Controller {
* {@link #onRequestPermissionsResult(int, String[], int[])} will be forwarded back to this Controller by the system.
*/
@TargetApi(Build.VERSION_CODES.M)
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
public final void requestPermissions(@NonNull final String[] permissions, final int requestCode) {
mRequestedPermissions.addAll(Arrays.asList(permissions));
getRouter().getLifecycleHandler().requestPermissions(mInstanceId, permissions, requestCode);
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { mRouter.getLifecycleHandler().requestPermissions(mInstanceId, permissions, requestCode); }
});
}
/**
@@ -440,8 +477,8 @@ public abstract class Controller {
/**
* Should be overridden if this Controller has requested runtime permissions and needs to handle the user's response.
*
* @param requestCode The requestCode that was used to request the permissions
* @param permissions The array of permissions requested
* @param requestCode The requestCode that was used to request the permissions
* @param permissions The array of permissions requested
* @param grantResults The results for each permission requested
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { }
@@ -533,6 +570,66 @@ public abstract class Controller {
mOverriddenPopHandler = overriddenPopHandler;
}
/**
* Registers/unregisters for participation in populating the options menu by receiving options-related
* callbacks, such as {@link #onCreateOptionsMenu(Menu, MenuInflater)}
*
* @param hasOptionsMenu If true, this controller's options menu callbacks will be called.
*/
public final void setHasOptionsMenu(boolean hasOptionsMenu) {
boolean invalidate = mAttached && !mOptionsMenuHidden && mHasOptionsMenu != hasOptionsMenu;
mHasOptionsMenu = hasOptionsMenu;
if (invalidate) {
mRouter.invalidateOptionsMenu();
}
}
/**
* Sets whether or not this controller's menu items should be visible. This is useful for hiding the
* controller's options menu items when its UI is hidden, and not just when it is detached from the
* window (the default).
*
* @param optionsMenuHidden Defaults to false. If true, this controller's menu items will not be shown.
*/
public final void setOptionsMenuHidden(boolean optionsMenuHidden) {
boolean invalidate = mAttached && mHasOptionsMenu && mOptionsMenuHidden != optionsMenuHidden;
mOptionsMenuHidden = optionsMenuHidden;
if (invalidate) {
mRouter.invalidateOptionsMenu();
}
}
/**
* Adds option items to the host Activity's standard options menu. This will only be called if
* {@link #setHasOptionsMenu(boolean)} has been called.
*
* @param menu The menu into which your options should be placed.
* @param inflater The inflater that can be used to inflate your menu items.
*/
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { }
/**
* Prepare the screen's options menu to be displayed. This is called directly before showing the
* menu and can be used modify its contents.
*
* @param menu The menu that will be displayed
*/
public void onPrepareOptionsMenu(Menu menu) { }
/**
* Called when an option menu item has been selected by the user.
*
* @param item The selected item.
* @return True if this event has been consumed, false if it has not.
*/
public boolean onOptionsItemSelected(MenuItem item) {
return false;
}
final void prepareForActivityPause() {
mNeedsAttach = mNeedsAttach || mAttached;
}
@@ -551,10 +648,29 @@ public abstract class Controller {
}
final void setRouter(@NonNull Router router) {
mRouter = router;
if (mRouter != router) {
mRouter = router;
for (ChildControllerTransaction child : mChildControllers) {
child.controller.setRouter(router);
performOnRestoreInstanceState();
for (RouterRequiringFunc listener : mOnRouterSetListeners) {
listener.execute();
}
mOnRouterSetListeners.clear();
for (ChildControllerTransaction child : mChildControllers) {
child.controller.setRouter(router);
}
} else {
performOnRestoreInstanceState();
}
}
final void executeWithRouter(@NonNull RouterRequiringFunc listener) {
if (mRouter != null) {
listener.execute();
} else {
mOnRouterSetListeners.add(listener);
}
}
@@ -631,6 +747,8 @@ public abstract class Controller {
}
private void attach(@NonNull View view) {
mHasSavedViewState = false;
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preAttach(this, view);
}
@@ -644,6 +762,10 @@ public abstract class Controller {
onAttach(view);
if (mHasOptionsMenu && !mOptionsMenuHidden) {
mRouter.invalidateOptionsMenu();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postAttach(this, view);
}
@@ -660,6 +782,10 @@ public abstract class Controller {
mAttached = false;
onDetach(view);
if (mHasOptionsMenu && !mOptionsMenuHidden) {
mRouter.invalidateOptionsMenu();
}
for (ChildControllerTransaction child : mChildControllers) {
ViewGroup container = (ViewGroup)mView.findViewById(child.containerId);
if (container != null) {
@@ -681,7 +807,7 @@ public abstract class Controller {
private void removeViewReference() {
if (mView != null) {
if (!mIsBeingDestroyed) {
if (!mIsBeingDestroyed && !mHasSavedViewState) {
saveViewState(mView);
}
@@ -745,6 +871,10 @@ public abstract class Controller {
mDestroyed = true;
if (mRouter != null) {
mRouter.getLifecycleHandler().unregisterForActivityRequests(mInstanceId);
}
onDestroy();
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
@@ -772,6 +902,8 @@ public abstract class Controller {
}
final void saveViewState(@NonNull View view) {
mHasSavedViewState = true;
mViewState = new Bundle();
SparseArray<Parcelable> hierarchyState = new SparseArray<>();
@@ -787,6 +919,10 @@ public abstract class Controller {
child.controller.saveViewState(child.controller.mView);
}
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onSaveViewState(this, mViewState);
}
}
final void restoreViewState(@NonNull View view) {
@@ -799,6 +935,10 @@ public abstract class Controller {
child.controller.restoreViewState(child.controller.mView);
}
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreViewState(this, mViewState);
}
}
}
@@ -807,8 +947,12 @@ public abstract class Controller {
detach(mView, mIsBeingDestroyed);
}
if (!mHasSavedViewState && mView != null) {
saveViewState(mView);
}
Bundle outState = new Bundle();
outState.putString(KEY_CLASS_NAME, getClass().getCanonicalName());
outState.putString(KEY_CLASS_NAME, getClass().getName());
outState.putBundle(KEY_VIEW_STATE, mViewState);
outState.putBundle(KEY_ARGS, mArgs);
outState.putString(KEY_INSTANCE_ID, mInstanceId);
@@ -855,11 +999,19 @@ public abstract class Controller {
addChildController(new ChildControllerTransaction(childBundle));
}
Bundle savedState = savedInstanceState.getBundle(KEY_SAVED_STATE);
onRestoreInstanceState(savedState);
mSavedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE);
performOnRestoreInstanceState();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreInstanceState(this, savedState);
private void performOnRestoreInstanceState() {
if (mSavedInstanceState != null && mRouter != null) {
onRestoreInstanceState(mSavedInstanceState);
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.onRestoreInstanceState(this, mSavedInstanceState);
}
mSavedInstanceState = null;
}
}
@@ -879,6 +1031,25 @@ public abstract class Controller {
}
}
final void createOptionsMenu(Menu menu, MenuInflater inflater) {
if (mAttached && mHasOptionsMenu && !mOptionsMenuHidden) {
onCreateOptionsMenu(menu, inflater);
}
}
final void prepareOptionsMenu(Menu menu) {
if (mAttached && mHasOptionsMenu && !mOptionsMenuHidden) {
onPrepareOptionsMenu(menu);
}
}
final boolean optionsItemSelected(MenuItem item) {
if (mAttached && mHasOptionsMenu && !mOptionsMenuHidden) {
return onOptionsItemSelected(item);
}
return false;
}
private void ensureRequiredConstructor() {
Constructor[] constructors = getClass().getConstructors();
if (getBundleConstructor(constructors) == null && getDefaultConstructor(constructors) == null) {
@@ -935,6 +1106,9 @@ public abstract class Controller {
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) { }
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) { }
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) { }
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) { }
}
}
@@ -2,7 +2,6 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -54,7 +53,7 @@ public abstract class ControllerChangeHandler {
final Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_CLASS_NAME, getClass().getCanonicalName());
bundle.putString(KEY_CLASS_NAME, getClass().getName());
Bundle savedState = new Bundle();
saveToBundle(savedState);
@@ -71,7 +70,7 @@ public abstract class ControllerChangeHandler {
}
}
public static ControllerChangeHandler fromBundle(@Nullable Bundle bundle) {
public static ControllerChangeHandler fromBundle(Bundle bundle) {
if (bundle != null) {
String className = bundle.getString(KEY_CLASS_NAME);
ControllerChangeHandler changeHandler = ClassUtils.newInstance(className);
@@ -13,16 +13,24 @@ public class ControllerTransaction {
*/
public enum ControllerChangeType {
/** The Controller is being pushed to the host container */
PUSH_ENTER,
PUSH_ENTER(true, true),
/** The Controller is being pushed to the backstack as another Controller is pushed to the host container */
PUSH_EXIT,
PUSH_EXIT(true, false),
/** The Controller is being popped from the backstack and placed in the host container as another Controller is popped */
POP_ENTER,
POP_ENTER(false, true),
/** The Controller is being popped from the host contianer */
POP_EXIT
/** The Controller is being popped from the host container */
POP_EXIT(false, false);
public boolean isPush;
public boolean isEnter;
ControllerChangeType(boolean isPush, boolean isEnter) {
this.isPush = isPush;
this.isEnter = isEnter;
}
}
private static final String KEY_VIEW_CONTROLLER_BUNDLE = "ControllerTransaction.controller.bundle";
@@ -4,6 +4,9 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -38,19 +41,15 @@ public class Router {
}
/**
* This should be called by the host Activity when its onActivityResult method is called. The call will be forwarded
* to the {@link Controller} with the instanceId passed in.
* This should be called by the host Activity when its onActivityResult method is called if the instanceId
* of the controller that called startActivityForResult is not known.
*
* @param instanceId The instanceId of the Controller to which this result should be forwarded
* @param requestCode The Activity's onActivityResult requestCode
* @param resultCode The Activity's onActivityResult resultCode
* @param data The Activity's onActivityResult data
*/
public void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.onActivityResult(requestCode, resultCode, data);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
getLifecycleHandler().onActivityResult(requestCode, resultCode, data);
}
/**
@@ -347,6 +346,13 @@ public class Router {
}
}
public final void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.onActivityResult(requestCode, resultCode, data);
}
}
public final void onActivityStarted(Activity activity) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.activityStarted(activity);
@@ -397,6 +403,38 @@ public class Router {
public final void onRestoreInstanceState(Bundle savedInstanceState) {
mBackStack.restoreInstanceState(savedInstanceState);
Iterator<RouterTransaction> backstackIterator = mBackStack.reverseIterator();
while (backstackIterator.hasNext()) {
backstackIterator.next().controller.setRouter(this);
}
}
public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.createOptionsMenu(menu, inflater);
}
}
public final void onPrepareOptionsMenu(Menu menu) {
for (RouterTransaction transaction : mBackStack) {
transaction.controller.prepareOptionsMenu(menu);
}
}
public final boolean onOptionsItemSelected(MenuItem item) {
for (RouterTransaction transaction : mBackStack) {
if (transaction.controller.optionsItemSelected(item)) {
return true;
}
}
return false;
}
final void invalidateOptionsMenu() {
if (mLifecycleHandler != null) {
mLifecycleHandler.getFragmentManager().invalidateOptionsMenu();
}
}
private void popToTransaction(@NonNull RouterTransaction transaction, ControllerChangeHandler changeHandler) {
@@ -50,8 +50,8 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
public void saveToBundle(@NonNull Bundle bundle) {
super.saveToBundle(bundle);
bundle.putString(KEY_TRANSITION_HANDLER_CLASS, mTransitionChangeHandler.getClass().getCanonicalName());
bundle.putString(KEY_FALLBACK_HANDLER_CLASS, mFallbackChangeHandler.getClass().getCanonicalName());
bundle.putString(KEY_TRANSITION_HANDLER_CLASS, mTransitionChangeHandler.getClass().getName());
bundle.putString(KEY_FALLBACK_HANDLER_CLASS, mFallbackChangeHandler.getClass().getName());
Bundle transitionBundle = new Bundle();
mTransitionChangeHandler.saveToBundle(transitionBundle);
@@ -9,6 +9,9 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Router;
@@ -33,6 +36,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public LifecycleHandler() {
setRetainInstance(true);
setHasOptionsMenu(true);
}
private static LifecycleHandler findInActivity(Activity activity) {
@@ -57,13 +61,16 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
Router router = mRouterMap.get(getRouterHashKey(container));
if (router == null) {
router = new Router();
router.setHost(this, container);
if (savedInstanceState != null) {
router.onRestoreInstanceState(savedInstanceState);
}
mRouterMap.put(getRouterHashKey(container), router);
} else {
router.setHost(this, container);
}
router.setHost(this, container);
return router;
}
@@ -155,13 +162,54 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return super.shouldShowRequestPermissionRationale(permission);
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode) {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
for (Router router : mRouterMap.values()) {
router.onCreateOptionsMenu(menu, inflater);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
for (Router router : mRouterMap.values()) {
router.onPrepareOptionsMenu(menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
for (Router router : mRouterMap.values()) {
if (router.onOptionsItemSelected(item)) {
return true;
}
}
return super.onOptionsItemSelected(item);
}
public void registerForActivityRequest(String instanceId, int requestCode) {
mActivityRequestMap.put(requestCode, instanceId);
}
public void unregisterForActivityRequests(String instanceId) {
for (int i = mActivityRequestMap.size() - 1; i >= 0; i--) {
if (instanceId.equals(mActivityRequestMap.get(mActivityRequestMap.keyAt(i)))) {
mActivityRequestMap.removeAt(i);
}
}
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode) {
registerForActivityRequest(instanceId, requestCode);
startActivityForResult(intent, requestCode);
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) {
mActivityRequestMap.put(requestCode, instanceId);
registerForActivityRequest(instanceId, requestCode);
startActivityForResult(intent, requestCode, options);
}
@@ -0,0 +1,5 @@
package com.bluelinelabs.conductor.internal;
public interface RouterRequiringFunc {
void execute();
}
@@ -0,0 +1,138 @@
package com.bluelinelabs.conductor;
import android.os.Parcel;
import android.os.Parcelable;
public class CallState implements Parcelable {
public int changeStartCalls;
public int changeEndCalls;
public int createViewCalls;
public int attachCalls;
public int destroyViewCalls;
public int detachCalls;
public int destroyCalls;
public int saveInstanceStateCalls;
public int restoreInstanceStateCalls;
public int saveViewStateCalls;
public int restoreViewStateCalls;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CallState callState = (CallState)o;
if (changeStartCalls != callState.changeStartCalls) {
return false;
}
if (changeEndCalls != callState.changeEndCalls) {
return false;
}
if (createViewCalls != callState.createViewCalls) {
return false;
}
if (attachCalls != callState.attachCalls) {
return false;
}
if (destroyViewCalls != callState.destroyViewCalls) {
return false;
}
if (detachCalls != callState.detachCalls) {
return false;
}
if (destroyCalls != callState.destroyCalls) {
return false;
}
if (saveInstanceStateCalls != callState.saveInstanceStateCalls) {
return false;
}
if (saveViewStateCalls != callState.saveViewStateCalls) {
return false;
}
if (restoreViewStateCalls != callState.restoreViewStateCalls) {
return false;
}
return restoreInstanceStateCalls == callState.restoreInstanceStateCalls;
}
@Override
public int hashCode() {
int result = changeStartCalls;
result = 31 * result + changeEndCalls;
result = 31 * result + createViewCalls;
result = 31 * result + attachCalls;
result = 31 * result + destroyViewCalls;
result = 31 * result + detachCalls;
result = 31 * result + destroyCalls;
result = 31 * result + saveInstanceStateCalls;
result = 31 * result + restoreInstanceStateCalls;
result = 31 * result + saveViewStateCalls;
result = 31 * result + restoreViewStateCalls;
return result;
}
@Override
public String toString() {
return "\nCallState{" +
"\n changeStartCalls=" + changeStartCalls +
"\n changeEndCalls=" + changeEndCalls +
"\n createViewCalls=" + createViewCalls +
"\n attachCalls=" + attachCalls +
"\n destroyViewCalls=" + destroyViewCalls +
"\n detachCalls=" + detachCalls +
"\n destroyCalls=" + destroyCalls +
"\n saveInstanceStateCalls=" + saveInstanceStateCalls +
"\n restoreInstanceStateCalls=" + restoreInstanceStateCalls +
"\n saveViewStateCalls=" + saveViewStateCalls +
"\n restoreViewStateCalls=" + restoreViewStateCalls +
"}\n";
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(changeStartCalls);
out.writeInt(changeEndCalls);
out.writeInt(createViewCalls);
out.writeInt(attachCalls);
out.writeInt(destroyViewCalls);
out.writeInt(detachCalls);
out.writeInt(destroyCalls);
out.writeInt(saveInstanceStateCalls);
out.writeInt(restoreInstanceStateCalls);
out.writeInt(saveViewStateCalls);
out.writeInt(restoreViewStateCalls);
}
public static final Parcelable.Creator<CallState> CREATOR = new Parcelable.Creator<CallState>() {
public CallState createFromParcel(Parcel in) {
CallState state = new CallState();
state.changeStartCalls = in.readInt();
state.changeEndCalls = in.readInt();
state.createViewCalls = in.readInt();
state.attachCalls = in.readInt();
state.destroyViewCalls = in.readInt();
state.detachCalls = in.readInt();
state.destroyCalls = in.readInt();
state.saveInstanceStateCalls = in.readInt();
state.restoreInstanceStateCalls = in.readInt();
state.saveViewStateCalls = in.readInt();
state.restoreViewStateCalls = in.readInt();
return state;
}
public CallState[] newArray(int size) {
return new CallState[size];
}
};
}
@@ -1,6 +1,8 @@
package com.bluelinelabs.conductor;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
@@ -24,78 +26,174 @@ import org.robolectric.util.ActivityController;
@Config(manifest = Config.NONE)
public class ControllerTests {
private Application mApplication;
private ActivityController<TestActivity> mActivityController;
private Router mRouter;
private int mChangeStartCalls;
private int mChangeEndCalls;
private int mCreateViewCalls;
private int mAttachCalls;
private int mDestroyViewCalls;
private int mDetachCalls;
private int mDestroyCalls;
private CallState mCurrentCallState;
public void createActivityController(Bundle savedInstanceState) {
mActivityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).withApplication(mApplication).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(mActivityController.get());
routerContainer.setId(containerId);
mRouter = Conductor.attachRouter(mActivityController.get(), routerContainer, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new TestController());
}
}
@Before
public void setup() {
mActivityController = Robolectric.buildActivity(TestActivity.class).create();
Activity activity = mActivityController.get();
mRouter = Conductor.attachRouter(activity, new FrameLayout(activity), null);
mRouter.setRoot(new TestController());
mApplication = new Application();
mChangeStartCalls = 0;
mChangeEndCalls = 0;
mCreateViewCalls = 0;
mAttachCalls = 0;
mDestroyViewCalls = 0;
mDestroyCalls = 0;
mDestroyCalls = 0;
createActivityController(null);
mCurrentCallState = new CallState();
}
@Test
public void testNormalLifecycle() {
Controller controller = new TestController();
TestController controller = new TestController();
attachLifecycleListener(controller);
assertCalls(0, 0, 0, 0, 0, 0, 0);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.popChangeHandler(getPopHandler(expectedCallState, controller))
.build()
);
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, controller);
mRouter.popCurrentController();
Assert.assertNull(controller.getView());
assertCalls(2, 2, 1, 1, 1, 1, 1);
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityDestroy() {
Controller controller = new TestController();
TestController controller = new TestController();
attachLifecycleListener(controller);
assertCalls(0, 0, 0, 0, 0, 0, 0);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.build()
);
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, controller);
mActivityController.pause();
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, controller);
mActivityController.stop();
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, controller);
mActivityController.destroy();
assertCalls(1, 1, 1, 1, 1, 1, 1);
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityConfigurationChange() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.tag("root")
.build()
);
assertCalls(expectedCallState, controller);
mActivityController.get().isChangingConfigurations = true;
Bundle bundle = new Bundle();
mActivityController.saveInstanceState(bundle);
expectedCallState.detachCalls++;
expectedCallState.saveViewStateCalls++;
expectedCallState.saveInstanceStateCalls++;
assertCalls(expectedCallState, controller);
mActivityController.pause();
assertCalls(expectedCallState, controller);
mActivityController.stop();
assertCalls(expectedCallState, controller);
mActivityController.destroy();
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
createActivityController(bundle);
controller = (TestController)mRouter.getControllerWithTag("root");
expectedCallState.restoreInstanceStateCalls++;
expectedCallState.restoreViewStateCalls++;
expectedCallState.changeStartCalls++;
expectedCallState.changeEndCalls++;
expectedCallState.createViewCalls++;
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
mCurrentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls;
mCurrentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls;
mCurrentCallState.changeStartCalls = controller.currentCallState.changeStartCalls;
mCurrentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
mCurrentCallState.createViewCalls = controller.currentCallState.createViewCalls;
assertCalls(expectedCallState, controller);
mActivityController.resume();
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityBackground() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, controller);
mRouter.pushController(RouterTransaction.builder(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.build()
);
assertCalls(expectedCallState, controller);
mActivityController.pause();
Bundle bundle = new Bundle();
mActivityController.saveInstanceState(bundle);
expectedCallState.detachCalls++;
expectedCallState.saveInstanceStateCalls++;
expectedCallState.saveViewStateCalls++;
assertCalls(expectedCallState, controller);
mActivityController.resume();
expectedCallState.createViewCalls++;
expectedCallState.restoreViewStateCalls++;
}
@Test
@@ -112,22 +210,24 @@ public class ControllerTests {
}))
.build());
Controller child = new TestController();
TestController child = new TestController();
attachLifecycleListener(child);
assertCalls(0, 0, 0, 0, 0, 0, 0);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, child);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child))
.build()
);
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, child);
parent.removeChildController(child);
assertCalls(2, 2, 1, 1, 1, 1, 1);
assertCalls(expectedCallState, child);
}
@Test
@@ -152,23 +252,28 @@ public class ControllerTests {
}))
.build());
Controller child = new TestController();
TestController child = new TestController();
attachLifecycleListener(child);
assertCalls(0, 0, 0, 0, 0, 0, 0);
CallState expectedCallState = new CallState();
assertCalls(expectedCallState, child);
parent.addChildController(ChildControllerTransaction.builder(child, TestController.VIEW_ID)
.pushChangeHandler(getPushHandler(0, 0, 0, 0, 0, 0, 0))
.popChangeHandler(getPopHandler(1, 1, 1, 1, 0, 0, 0))
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child))
.build()
);
assertCalls(1, 1, 1, 1, 0, 0, 0);
assertCalls(expectedCallState, child);
mRouter.popCurrentController();
ViewUtils.setAttached(child.getView(), false);
assertCalls(1, 1, 1, 1, 1, 1, 1);
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, child);
}
@Test
@@ -199,77 +304,110 @@ public class ControllerTests {
Assert.assertNull(controller.getView());
}
private ChangeHandler getPushHandler(final int changeStart, final int changeEnd, final int bindView, final int attach, final int unbindView, final int detach, final int destroy) {
private ChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
assertCalls(changeStart + 1, changeEnd, bindView + 1, attach, unbindView, detach, destroy);
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
assertCalls(expectedCallState, controller);
container.addView(to);
ViewUtils.setAttached(to, true);
assertCalls(changeStart + 1, changeEnd, bindView + 1, attach + 1, unbindView, detach, destroy);
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
changeListener.onChangeCompleted();
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private ChangeHandler getPopHandler(final int changeStart, final int changeEnd, final int bindView, final int attach, final int unbindView, final int detach, final int destroy) {
private ChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView, detach, destroy);
expectedCallState.changeStartCalls++;
assertCalls(expectedCallState, controller);
container.removeView(from);
ViewUtils.setAttached(from, false);
assertCalls(changeStart + 1, changeEnd, bindView, attach, unbindView + 1, detach + 1, destroy + 1);
expectedCallState.destroyViewCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
changeListener.onChangeCompleted();
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private void assertCalls(int changeStart, int changeEnd, int bindView, int attach, int unbindView, int detach, int destroy) {
Assert.assertEquals(changeStart, mChangeStartCalls);
Assert.assertEquals(changeEnd, mChangeEndCalls);
Assert.assertEquals(bindView, mCreateViewCalls);
Assert.assertEquals(attach, mAttachCalls);
Assert.assertEquals(unbindView, mDestroyViewCalls);
Assert.assertEquals(detach, mDetachCalls);
Assert.assertEquals(destroy, mDestroyCalls);
private void assertCalls(CallState callState, TestController controller) {
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
Assert.assertEquals("Expected call counts and lifecycle call counts do not match.", callState, mCurrentCallState);
}
private void attachLifecycleListener(Controller controller) {
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mChangeStartCalls++;
mCurrentCallState.changeStartCalls++;
}
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
mChangeEndCalls++;
mCurrentCallState.changeEndCalls++;
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
mCreateViewCalls++;
mCurrentCallState.createViewCalls++;
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
mAttachCalls++;
mCurrentCallState.attachCalls++;
}
@Override
public void postDestroyView(@NonNull Controller controller) {
mDestroyViewCalls++;
mCurrentCallState.destroyViewCalls++;
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
mDetachCalls++;
mCurrentCallState.detachCalls++;
}
@Override
public void postDestroy(@NonNull Controller controller) {
mDestroyCalls++;
mCurrentCallState.destroyCalls++;
}
@Override
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
mCurrentCallState.saveInstanceStateCalls++;
}
@Override
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
mCurrentCallState.restoreInstanceStateCalls++;
}
@Override
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
mCurrentCallState.saveViewStateCalls++;
}
@Override
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
mCurrentCallState.restoreViewStateCalls++;
}
});
}
@@ -293,4 +431,5 @@ public class ControllerTests {
mListener.performChange(container, from, to, isPush, changeListener);
}
}
}
@@ -3,4 +3,12 @@ package com.bluelinelabs.conductor;
import android.app.Activity;
public class TestActivity extends Activity {
public boolean isChangingConfigurations = false;
@Override
public boolean isChangingConfigurations() {
return isChangingConfigurations;
}
}
@@ -1,5 +1,6 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
@@ -7,18 +8,92 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
public class TestController extends Controller {
@IdRes public static final int VIEW_ID = 2342;
public TestController() { }
private static final String KEY_CALL_STATE = "TestController.currentCallState";
public CallState currentCallState;
public TestController() {
currentCallState = new CallState();
}
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
currentCallState.createViewCalls++;
View view = new FrameLayout(inflater.getContext());
view.setId(VIEW_ID);
return view;
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeStarted(changeHandler, changeType);
currentCallState.changeStartCalls++;
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
currentCallState.changeEndCalls++;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
currentCallState.attachCalls++;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
currentCallState.detachCalls++;
}
@Override
protected void onDestroyView(View view) {
super.onDestroyView(view);
currentCallState.destroyViewCalls++;
}
@Override
protected void onDestroy() {
super.onDestroy();
currentCallState.destroyCalls++;
}
@Override
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
super.onSaveViewState(view, outState);
currentCallState.saveViewStateCalls++;
}
@Override
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
super.onRestoreViewState(view, savedViewState);
currentCallState.restoreViewStateCalls++;
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
currentCallState.saveInstanceStateCalls++;
outState.putParcelable(KEY_CALL_STATE, currentCallState);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
currentCallState = savedInstanceState.getParcelable(KEY_CALL_STATE);
currentCallState.restoreInstanceStateCalls++;
}
}
@@ -0,0 +1,7 @@
package com.bluelinelabs.conductor.demo;
import android.support.v7.app.ActionBar;
public interface ActionBarProvider {
ActionBar getSupportActionBar();
}
@@ -1,7 +1,8 @@
package com.bluelinelabs.conductor.demo;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Conductor;
@@ -11,8 +12,9 @@ import com.bluelinelabs.conductor.demo.controllers.HomeController;
import butterknife.Bind;
import butterknife.ButterKnife;
public class MainActivity extends Activity {
public class MainActivity extends AppCompatActivity implements ActionBarProvider {
@Bind(R.id.toolbar) Toolbar mToolbar;
@Bind(R.id.controller_container) ViewGroup mContainer;
private Router mRouter;
@@ -24,6 +26,8 @@ public class MainActivity extends Activity {
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(mToolbar);
mRouter = Conductor.attachRouter(this, mContainer, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new HomeController());
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor.changehandler;
package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.annotation.TargetApi;
@@ -9,6 +9,8 @@ import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler;
/**
* An {@link AnimatorChangeHandler} that will perform a circular reveal
*/
@@ -0,0 +1,36 @@
package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
public class CircularRevealChangeHandlerCompat extends CircularRevealChangeHandler {
public CircularRevealChangeHandlerCompat() { }
public CircularRevealChangeHandlerCompat(@NonNull View fromView, @NonNull View containerView) {
super(fromView, containerView);
}
@Override
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return super.getAnimator(container, from, to, isPush, toAddedToContainer);
} else {
AnimatorSet animator = new AnimatorSet();
if (to != null && toAddedToContainer) {
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
}
if (from != null) {
animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0));
}
return animator;
}
}
}
@@ -10,11 +10,11 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
public class ChildController extends RefWatchingController {
public class ChildController extends BaseController {
private static final String KEY_TITLE = "ChildController.title";
private static final String KEY_BG_COLOR = "ChildController.bgColor";
@@ -10,14 +10,14 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ScaleFadeChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout.ElasticDragDismissCallback;
import butterknife.Bind;
@TargetApi(VERSION_CODES.LOLLIPOP)
public class DragDismissController extends RefWatchingController {
public class DragDismissController extends BaseController {
@Bind(R.id.tv_lorem_ipsum) TextView mTvLoremIpsum;
@@ -52,4 +52,8 @@ public class DragDismissController extends RefWatchingController {
((ElasticDragDismissFrameLayout)view).removeListener(mDragDismissListener);
}
@Override
protected String getTitle() {
return "Drag to Dismiss";
}
}
@@ -1,28 +1,40 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.Intent;
import android.graphics.PorterDuff.Mode;
import android.net.Uri;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bluelinelabs.conductor.ChildControllerTransaction;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class HomeController extends RefWatchingController {
public class HomeController extends BaseController {
public enum HomeDemoModel {
NAVIGATION("Navigation Demos", R.color.red_300),
@@ -45,6 +57,10 @@ public class HomeController extends RefWatchingController {
@Bind(R.id.recycler_view) RecyclerView mRecyclerView;
public HomeController() {
setHasOptionsMenu(true);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -60,6 +76,58 @@ public class HomeController extends RefWatchingController {
mRecyclerView.setAdapter(new HomeAdapter(LayoutInflater.from(view.getContext()), HomeDemoModel.values()));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.home, menu);
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
setOptionsMenuHidden(!changeType.isEnter);
if (changeType.isEnter) {
setTitle();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.about) {
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
final String url = "https://github.com/bluelinelabs/Conductor";
SpannableString link = new SpannableString(url);
link.setSpan(new URLSpan(url) {
@Override
public void onClick(View widget) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}, 0, link.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
SpannableStringBuilder content = new SpannableStringBuilder();
content.append("Conductor");
content.append("\n\n");
content.append(details);
content.append("\n\n");
content.append(link);
addChildController(ChildControllerTransaction.builder(new OverlayController(content), R.id.home_root)
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.addToLocalBackstack(true)
.build());
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected String getTitle() {
return "Conductor Demos";
}
void onModelRowClick(HomeDemoModel model) {
switch (model) {
case NAVIGATION:
@@ -91,7 +159,7 @@ public class HomeController extends RefWatchingController {
.build());
break;
case OVERLAY:
addChildController(ChildControllerTransaction.builder(new OverlayController(), R.id.home_root)
addChildController(ChildControllerTransaction.builder(new OverlayController("I'm an Overlay!"), R.id.home_root)
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.addToLocalBackstack(true)
@@ -9,15 +9,15 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import butterknife.Bind;
import butterknife.OnClick;
public class NavigationDemoController extends RefWatchingController {
public class NavigationDemoController extends BaseController {
public static final String TAG_UP_TRANSACTION = "NavigationDemoController.up";
@@ -52,6 +52,11 @@ public class NavigationDemoController extends RefWatchingController {
mTvTitle.setText(getResources().getString(R.string.navigation_title, mIndex));
}
@Override
protected String getTitle() {
return "Navigation Demos";
}
@OnClick(R.id.btn_next) void onNextClicked() {
getRouter().pushController(RouterTransaction.builder(new NavigationDemoController(mIndex + 1))
.pushChangeHandler(new HorizontalChangeHandler())
@@ -1,20 +1,35 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.Bind;
public class OverlayController extends RefWatchingController {
public class OverlayController extends BaseController {
private static final String KEY_TEXT = "OverlayController.text";
@Bind(R.id.text_view) TextView mTextView;
public OverlayController(CharSequence text) {
this(new BundleBuilder(new Bundle())
.putCharSequence(KEY_TEXT, text)
.build());
}
public OverlayController(Bundle args) {
super(args);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -24,7 +39,8 @@ public class OverlayController extends RefWatchingController {
@Override
public void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTextView.setText("I'm an Overlay");
mTextView.setText(getArgs().getCharSequence(KEY_TEXT));
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
@@ -1,6 +1,10 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.design.widget.TabLayout;
import android.support.design.widget.TabLayout.OnTabSelectedListener;
import android.support.design.widget.TabLayout.Tab;
import android.support.design.widget.TabLayout.TabLayoutOnPageChangeListener;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
@@ -8,35 +12,35 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.support.ControllerPagerAdapter;
import butterknife.Bind;
public class PagerController extends RefWatchingController {
public class PagerController extends BaseController {
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
@Bind(R.id.tab_layout) TabLayout mTabLayout;
@Bind(R.id.view_pager) ViewPager mViewPager;
private ControllerPagerAdapter mPagerAdapter;
public PagerController() {
mPagerAdapter = new ControllerPagerAdapter(this) {
@Override
public Controller getItem(int position) {
switch (position) {
case 0:
return new ChildController("Child #1 (Swipe to see more)", R.color.cyan_300, true);
case 1:
return new ChildController("Child #2 (Swipe to see more)", R.color.deep_purple_300, true);
case 2:
return new ChildController("Child #3 (Swipe to see more)", R.color.lime_300, true);
default:
throw new RuntimeException("Invalid item position: " + position);
}
return new ChildController(String.format("Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
}
@Override
public int getCount() {
return 3;
return PAGE_COLORS.length;
}
@Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
};
}
@@ -44,7 +48,22 @@ public class PagerController extends RefWatchingController {
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
mTabLayout.setTabsFromPagerAdapter(mPagerAdapter);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(mTabLayout));
mTabLayout.setOnTabSelectedListener(new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
mViewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(Tab tab) { }
@Override
public void onTabReselected(Tab tab) { }
});
}
@NonNull
@@ -53,4 +72,9 @@ public class PagerController extends RefWatchingController {
return inflater.inflate(R.layout.controller_pager, container, false);
}
@Override
protected String getTitle() {
return "ViewPager Demo";
}
}
@@ -11,10 +11,10 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerTransaction.ControllerChangeType;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
public class ParentController extends RefWatchingController {
public class ParentController extends BaseController {
private static final int NUMBER_OF_CHILDREN = 5;
private boolean mFinishing;
@@ -62,6 +62,11 @@ public class ParentController extends RefWatchingController {
return true;
}
@Override
protected String getTitle() {
return "Parent/Child Demo";
}
@Override
public void addChildController(ChildControllerTransaction transaction) {
final int index = Integer.parseInt(transaction.tag);
@@ -10,7 +10,7 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.rxlifecycle.ControllerEvent;
import java.util.concurrent.TimeUnit;
@@ -23,7 +23,7 @@ import rx.functions.Action1;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class RxLifecycleController extends RefWatchingController {
public class RxLifecycleController extends BaseController {
private static final String TAG = "RxLifecycleController";
@@ -112,6 +112,11 @@ public class RxLifecycleController extends RefWatchingController {
Log.i(TAG, "onDestroy() called");
}
@Override
protected String getTitle() {
return "RxLifecycle Demo";
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
@@ -17,12 +17,12 @@ import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.TargetTitleEntryController.TargetTitleEntryControllerListener;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.OnClick;
public class TargetDisplayController extends RefWatchingController implements TargetTitleEntryControllerListener {
public class TargetDisplayController extends BaseController implements TargetTitleEntryControllerListener {
private static final int REQUEST_SELECT_IMAGE = 126;
@@ -94,6 +94,11 @@ public class TargetDisplayController extends RefWatchingController implements Ta
}
}
@Override
protected String getTitle() {
return "Target Controller Demo";
}
private void setImageView() {
mImageView.setImageURI(mImageUri);
}
@@ -8,12 +8,12 @@ import android.widget.EditText;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
import butterknife.OnClick;
public class TargetTitleEntryController extends RefWatchingController {
public class TargetTitleEntryController extends BaseController {
public interface TargetTitleEntryControllerListener {
void onTitlePicked(String option);
@@ -22,22 +22,22 @@ public class TargetTitleEntryController extends RefWatchingController {
@Bind(R.id.edit_text) EditText mEditText;
public <T extends Controller & TargetTitleEntryControllerListener> TargetTitleEntryController(T targetController) {
super.setTargetController(targetController);
setTargetController(targetController);
}
public TargetTitleEntryController() { }
@Override
public void setTargetController(Controller target) {
throw new RuntimeException(getClass().getSimpleName() + "s can only have their target set through the constructor.");
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_target_title_entry, container, false);
}
@Override
protected String getTitle() {
return "Target Controller Demo";
}
@OnClick(R.id.btn_use_title) void optionPicked() {
Controller targetController = getTargetController();
if (targetController != null) {
@@ -9,11 +9,11 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.Bind;
public class TextController extends RefWatchingController {
public class TextController extends BaseController {
private static final String KEY_TEXT = "TextController.text";
@@ -15,27 +15,27 @@ import android.widget.TextView;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.CircularRevealChangeHandler;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.CircularRevealChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.FlipChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.RefWatchingController;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class TransitionDemoController extends RefWatchingController {
public class TransitionDemoController extends BaseController {
private static final String KEY_INDEX = "TransitionDemoController.index";
public enum TransitionDemo {
VERTICAL("Vertical Slide Animation", R.layout.controller_transition_demo, R.color.blue_grey_300),
CIRCULAR("Circular Reveal Animation", R.layout.controller_transition_demo, R.color.red_300),
CIRCULAR("Circular Reveal Animation (on Lollipop and above, else Fade)", R.layout.controller_transition_demo, R.color.red_300),
FADE("Fade Animation", R.layout.controller_transition_demo, R.color.blue_300),
FLIP("Flip Animation", R.layout.controller_transition_demo, R.color.deep_orange_300),
HORIZONTAL("Horizontal Slide Animation", R.layout.controller_transition_demo, R.color.green_300),
@@ -101,6 +101,11 @@ public class TransitionDemoController extends RefWatchingController {
mTvTitle.setText(mTransitionDemo.title);
}
@Override
protected String getTitle() {
return "Transition Demos";
}
@OnClick(R.id.btn_next) void onNextClicked() {
final int nextIndex = mTransitionDemo.ordinal() + 1;
@@ -117,7 +122,7 @@ public class TransitionDemoController extends RefWatchingController {
return new VerticalChangeHandler();
case CIRCULAR:
TransitionDemoController demoController = (TransitionDemoController)from;
return new CircularRevealChangeHandler(demoController.mBtnNext, demoController.mContainerView);
return new CircularRevealChangeHandlerCompat(demoController.mBtnNext, demoController.mContainerView);
case FADE:
return new FadeChangeHandler();
case FLIP:
@@ -0,0 +1,42 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.view.View;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
public abstract class BaseController extends RefWatchingController {
protected BaseController() { }
protected BaseController(Bundle args) {
super(args);
}
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected ActionBar getActionBar() {
ActionBarProvider actionBarProvider = ((ActionBarProvider)getActivity());
return actionBarProvider != null ? actionBarProvider.getSupportActionBar() : null;
}
@Override
protected void onAttach(@NonNull View view) {
setTitle();
super.onAttach(view);
}
protected void setTitle() {
String title = getTitle();
ActionBar actionBar = getActionBar();
if (title != null && actionBar != null) {
actionBar.setTitle(title);
}
}
protected String getTitle() {
return null;
}
}
@@ -16,13 +16,12 @@
package com.bluelinelabs.conductor.demo.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import java.util.ArrayList;
@@ -33,8 +32,30 @@ import java.util.List;
* Applies an elasticity factor to reduce movement as you approach the given dismiss distance.
* Optionally also scales down content during drag.
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
public class ElasticDragDismissFrameLayout extends FrameLayout {
public class ElasticDragDismissFrameLayout extends FrameLayout implements NestedScrollingParent {
public static abstract class ElasticDragDismissCallback {
/**
* Called for each drag event.
*
* @param elasticOffset Indicating the drag offset with elasticity applied i.e. may
* exceed 1.
* @param elasticOffsetPixels The elastically scaled drag distance in pixels.
* @param rawOffset Value from [0, 1] indicating the raw drag offset i.e.
* without elasticity applied. A value of 1 indicates that the
* dismiss distance has been reached.
* @param rawOffsetPixels The raw distance the user has dragged
*/
public void onDrag(float elasticOffset, float elasticOffsetPixels,
float rawOffset, float rawOffsetPixels) { }
/**
* Called when dragging is released and has exceeded the threshold dismiss distance.
*/
public void onDragDismissed() { }
}
// configurable attribs
private float dragDismissDistance = Float.MAX_VALUE;
@@ -51,21 +72,16 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
private List<ElasticDragDismissCallback> callbacks;
public ElasticDragDismissFrameLayout(Context context) {
this(context, null, 0, 0);
this(context, null, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
this(context, attrs, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ElasticDragDismissFrameLayout(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
super(context, attrs, defStyleAttr);
dragDismissDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics());
dragDismissFraction = 0.7f;
@@ -73,29 +89,6 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
shouldScale = true;
}
public static abstract class ElasticDragDismissCallback {
/**
* Called for each drag event.
*
* @param elasticOffset Indicating the drag offset with elasticity applied i.e. may
* exceed 1.
* @param elasticOffsetPixels The elastically scaled drag distance in pixels.
* @param rawOffset Value from [0, 1] indicating the raw drag offset i.e.
* without elasticity applied. A value of 1 indicates that the
* dismiss distance has been reached.
* @param rawOffsetPixels The raw distance the user has dragged
*/
public void onDrag(float elasticOffset, float elasticOffsetPixels,
float rawOffset, float rawOffsetPixels) { }
/**
* Called when dragging is released and has exceeded the threshold dismiss distance.
*/
public void onDragDismissed() { }
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
@@ -126,7 +119,7 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
.scaleX(1f)
.scaleY(1f)
.setDuration(200L)
.setInterpolator(AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in))
.setInterpolator(new FastOutSlowInInterpolator())
.setListener(null)
.start();
totalDrag = 0;
@@ -135,6 +128,24 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public int getNestedScrollAxes() {
return 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) { }
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -171,6 +182,9 @@ public class ElasticDragDismissFrameLayout extends FrameLayout {
draggingUp = true;
if (shouldScale) setPivotY(0f);
}
setPivotX(getWidth() / 2);
// how far have we dragged relative to the distance to perform a dismiss
// (01 where 1 = dismiss distance). Decreasing logarithmically as we approach the limit
float dragFraction = (float) Math.log10(1 + (Math.abs(totalDrag) / dragDismissDistance));
+26 -4
View File
@@ -1,9 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/controller_container"
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bluelinelabs.conductor.demo.MainActivity"
/>
android:fitsSystemWindows="true"
tools:context="com.bluelinelabs.conductor.demo.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
/>
</android.support.design.widget.AppBarLayout>
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/controller_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
@@ -14,17 +14,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="28sp"
android:textSize="22sp"
android:padding="16dp"
android:text="@string/drag_to_dismiss"
/>
<ScrollView
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:fillViewport="true"
android:nestedScrollingEnabled="true"
android:overScrollFooter="@android:color/transparent"
android:overScrollMode="never" >
@@ -36,7 +35,7 @@
android:padding="16dp"
/>
</ScrollView>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
</com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout>
@@ -4,20 +4,21 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#88000000"
android:clickable="true" >
android:clickable="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="32dp"
android:background="@android:color/white" >
android:background="@android:color/white">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
/>
</FrameLayout>
@@ -3,16 +3,16 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green_300"
android:orientation="vertical" >
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="28sp"
android:padding="16dp"
android:text="@string/view_pager"
android:background="?attr/colorPrimary"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
/>
<android.support.v4.view.ViewPager
@@ -16,10 +16,10 @@
<ImageView
android:id="@+id/image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_width="@dimen/display_target_image_size"
android:layout_height="@dimen/display_target_image_size"
android:layout_gravity="center_horizontal"
android:layout_margin="24dp"
android:layout_margin="@dimen/display_target_image_margin"
/>
<LinearLayout
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@+id/about"
android:title="@string/about"
app:showAsAction="always"
/>
</menu>
+8
View File
@@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">24dp</dimen>
<dimen name="activity_vertical_margin">24dp</dimen>
<dimen name="display_target_image_size">120dp</dimen>
<dimen name="display_target_image_margin">16dp</dimen>
</resources>
+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#78909c</color>
<color name="colorPrimaryDark">#37474f</color>
<color name="colorPrimary">#263239</color>
<color name="colorPrimaryDark">#21272c</color>
<color name="colorAccent">#1976d2</color>
<color name="white">@android:color/white</color>
+3
View File
@@ -2,4 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="display_target_image_size">200dp</dimen>
<dimen name="display_target_image_margin">24dp</dimen>
</resources>
+4 -4
View File
@@ -2,6 +2,9 @@
<resources>
<string name="app_name">Conductor Demo</string>
<!-- Home Controller -->
<string name="about">About</string>
<!-- Navigation Demo -->
<string name="pop_to_root">Pop to Root</string>
<string name="go_up">Go Up</string>
@@ -13,9 +16,6 @@
<string name="transition_tag_dot">transition.dot</string>
<string name="transition_tag_title">transition.title</string>
<!-- View Pager Demo -->
<string name="view_pager">View Pager</string>
<!-- Parent Controller Demo -->
<string name="parent_controller">Parent Controller</string>
<string name="go_to_next_parent">Go to next parent</string>
@@ -27,7 +27,7 @@
<string name="use_title">Use Title</string>
<!-- Drag Dismiss Demo -->
<string name="drag_to_dismiss">Drag to Dismiss</string>
<string name="drag_to_dismiss">Elastic scrolling view - scroll past bounds to dismiss.</string>
<!-- RxLifecycle Demo -->
<string name="rxlifecycle_title">Watch tag %1$s on logcat for a demo.</string>
+2 -2
View File
@@ -5,10 +5,10 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@color/white</item>
<item name="android:textViewStyle">@style/TextView</item>
<item name="android:textViewStyle">@style/AppTheme.TextView</item>
</style>
<style name="TextView" parent="@android:style/Widget.TextView">
<style name="AppTheme.TextView" parent="@android:style/Widget.TextView">
<item name="android:textSize">20sp</item>
</style>
+4 -4
View File
@@ -5,8 +5,8 @@ ext {
buildToolsVersion = '23.0.2'
versionCode = 1
versionName = '1.1.1'
publishedVersionName = '1.1.1'
versionName = '1.1.4'
publishedVersionName = '1.1.3'
supportV4 = 'com.android.support:support-v4:23.1.1'
supportDesign = 'com.android.support:design:23.1.1'
@@ -15,8 +15,8 @@ ext {
butterknife = 'com.jakewharton:butterknife:7.0.1'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
rxJava = 'io.reactivex:rxjava:1.1.0'
rxAndroid = 'io.reactivex:rxandroid:1.1.0'