Compare commits

...

4 Commits

Author SHA1 Message Date
Eric Kuck f0a488c711 Added options menu support for controllers
Updated demo app to have a toolbar
2016-04-05 13:34:14 -05:00
Eric Kuck 511c229364 Fixed issue with re-attaching when the host Activity never left with window 2016-04-04 23:23:01 -05:00
Eric Kuck b5d0e46740 Now enforces only setting a controller's target one time. 2016-04-04 17:45:29 -05:00
Eric Kuck 8f1be7fe21 Updated readme to reflect latest lifecycle updates 2016-04-04 17:29:22 -05:00
31 changed files with 517 additions and 110 deletions
+11 -16
View File
@@ -14,20 +14,20 @@ A small, yet full-featured framework that allows building View-based Android app
:floppy_disk: | State persistence
:phone: | Callbacks for onActivityResult, onRequestPermissionsResult, etc
:european_post_office: | MVP / MVVM / VIPER / MVC ready
Conductor is architecture-agnostic and does not try to force any design decisions on the developer. We here at BlueLine Labs tend to use either MVP or MVVM, but it would work equally well with standard MVC or whatever else you want to throw at it.
## Installation
```gradle
compile 'com.bluelinelabs:conductor:1.1.0'
compile 'com.bluelinelabs:conductor:1.1.2'
// 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.2'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.0'
compile 'com.bluelinelabs:conductor-rxlifecycle:1.1.2'
```
## Components to Know
@@ -53,9 +53,9 @@ public class MainActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup container = (ViewGroup)findViewById(R.id.controller_container)
ViewGroup container = (ViewGroup)findViewById(R.id.controller_container)
mRouter = Conductor.attachRouter(this, container, savedInstanceState);
if (!mRouter.hasRootController()) {
mRouter.setRoot(new HomeController());
@@ -78,15 +78,10 @@ public class MainActivity extends Activity {
public class HomeController extends Controller {
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_home, container, false);
}
@Override
public void onBindView(@NonNull View view) {
super.onBindView(view);
((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
View view = inflater.inflate(R.layout.controller_home, container, false);
((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
return view;
}
}
@@ -12,7 +12,7 @@ import com.bluelinelabs.conductor.internal.LifecycleHandler;
*/
public final class Conductor {
private Conductor(){}
private Conductor() {}
/**
* Conductor will create a {@link Router} that has been initialized for your Activity and containing ViewGroup.
@@ -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;
@@ -53,6 +56,9 @@ public abstract class Controller {
private boolean mIsBeingDestroyed;
private boolean mDestroyed;
private boolean mAttached;
private boolean mHasOptionsMenu;
private boolean mOptionsMenuHidden;
private boolean mViewIsAttached;
private Router mRouter;
private View mView;
private Controller mParentController;
@@ -111,7 +117,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.
@@ -278,6 +284,10 @@ public abstract class Controller {
* @param target The Controller that is the target of this one.
*/
public void setTargetController(Controller target) {
if (mTargetInstanceId != null) {
throw new RuntimeException("Target controller already set. A controller's target may only be set once.");
}
mTargetInstanceId = target != null ? target.getInstanceId() : null;
}
@@ -302,7 +312,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) { }
@@ -310,7 +320,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) { }
@@ -358,7 +368,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) { }
@@ -367,7 +377,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) { }
@@ -387,6 +397,13 @@ public abstract class Controller {
*/
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { }
/**
* Calls startActivity(Intent) from this Controller's host Activity.
*/
public final void startActivity(Intent intent) {
getRouter().getLifecycleHandler().startActivity(intent);
}
/**
* Calls startActivityForResult(Intent, int) from this Controller's host Activity.
*/
@@ -406,8 +423,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) { }
@@ -435,8 +452,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) { }
@@ -528,6 +545,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;
}
@@ -586,6 +663,10 @@ public abstract class Controller {
}
final void activityResumed(Activity activity) {
if (!mAttached && mView != null && mViewIsAttached) {
attach(mView);
}
onActivityResumed(activity);
for (ChildControllerTransaction child : mChildControllers) {
@@ -635,12 +716,18 @@ public abstract class Controller {
onAttach(view);
if (mHasOptionsMenu && !mOptionsMenuHidden) {
mRouter.invalidateOptionsMenu();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postAttach(this, view);
}
}
private void detach(@NonNull View view) {
private void detach(@NonNull View view, boolean allowViewRefRemoval) {
final boolean removeViewRef = allowViewRefRemoval && (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mIsBeingDestroyed);
if (mAttached) {
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.preDetach(this, view);
@@ -649,6 +736,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) {
@@ -656,13 +747,15 @@ public abstract class Controller {
}
}
if (mRetainViewMode == RetainViewMode.RELEASE_DETACH || mIsBeingDestroyed) {
if (removeViewRef) {
removeViewReference();
}
for (LifecycleListener lifecycleListener : mLifecycleListeners) {
lifecycleListener.postDetach(this, view);
}
} else if (removeViewRef) {
removeViewReference();
}
}
@@ -703,12 +796,16 @@ public abstract class Controller {
mView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (v == mView) {
mViewIsAttached = true;
}
attach(v);
}
@Override
public void onViewDetachedFromWindow(View v) {
detach(v);
mViewIsAttached = false;
detach(v, true);
}
});
@@ -750,7 +847,7 @@ public abstract class Controller {
if (!mAttached) {
removeViewReference();
} else if (removeViews) {
detach(mView);
detach(mView, true);
}
}
@@ -787,7 +884,7 @@ public abstract class Controller {
final Bundle detachAndSaveInstanceState() {
if (mAttached && mView != null) {
detach(mView);
detach(mView, mIsBeingDestroyed);
}
Bundle outState = new Bundle();
@@ -862,6 +959,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) {
@@ -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;
@@ -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;
@@ -399,6 +402,33 @@ public class Router {
mBackStack.restoreInstanceState(savedInstanceState);
}
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) {
RouterTransaction topTransaction = mBackStack.peek();
List<RouterTransaction> poppedTransactions = mBackStack.popTo(transaction);
@@ -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) {
@@ -155,6 +159,35 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return super.shouldShowRequestPermissionRationale(permission);
}
@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 startActivityForResult(String instanceId, Intent intent, int requestCode) {
mActivityRequestMap.put(requestCode, instanceId);
startActivityForResult(intent, requestCode);
@@ -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());
@@ -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";
@@ -19,17 +19,17 @@ 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.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";
@@ -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;
@@ -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;
}
}
+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,7 +14,7 @@
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"
/>
@@ -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
+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>
+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>
+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>
+2 -2
View File
@@ -5,8 +5,8 @@ ext {
buildToolsVersion = '23.0.2'
versionCode = 1
versionName = '1.1.0'
publishedVersionName = '1.1.0'
versionName = '1.1.2'
publishedVersionName = '1.1.2'
supportV4 = 'com.android.support:support-v4:23.1.1'
supportDesign = 'com.android.support:design:23.1.1'