diff --git a/CHANGELOG.md b/CHANGELOG.md index f14f81b..4652390 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Change Log ========== +Version 1.1.0 +---------------------------- + * Added AnimationHandler a simple api to handle generic transition animations. + * Moved enums to IntDef for library size optimization. + * Changed nested class access to private methods & attributes to package private as part of library size optimization. + * Migrated AutoParcel to AutoValue. + * Deprecated HandlesBackPresses - this can be handled in a custom callback from onBackPress in you activity. + * Deprecated HasTraversalAnimation - use AnimationHandler. + * Call to replace with 0 views will result in a call to push. + * Added replaceStack with one view method. + Version 1.0.0 ---------------------------- * Added safe operations helper class. diff --git a/README.md b/README.md index af60150..e3df8c3 100755 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Defrag is available in the jCenter repository: ```java dependencies { - compile 'com.solera.defrag:defrag:1.0.0' + compile 'com.solera.defrag:defrag:1.1.0' } ``` @@ -44,32 +44,32 @@ is easy! Custom transition animations --- -It's easy to create custom transition animations, all you have to do is implement HasTraversalAnimation: +It's easy to create custom transition animations, just call setAnimationHandler with your own AnimationHandler: ```java -public class ViewWithTraversalAnimation extends FrameLayout implements HasTraversalAnimation { -... - /** - * @param fromView the view that we are traversing from. - * @return an animation that will be run for the traversal, or null if the default should be run. - */ - @Override @Nullable Animator createAnimation(@NonNull View fromView) { - final AnimatorSet set = new AnimatorSet(); - - final AnimatorSet exitAnim = new AnimatorSet(); - exitAnim.setInterpolator(new AccelerateInterpolator()); - exitAnim.play(ObjectAnimator.ofFloat(fromView, View.ALPHA, 0.0f)) - .with(ObjectAnimator.ofFloat(fromView, View.TRANSLATION_X, 0f, -200)); - - setAlpha(0f); - final AnimatorSet enterAnim = new AnimatorSet(); - enterAnim.setInterpolator(new DecelerateInterpolator()); - enterAnim.setStartDelay(300); - enterAnim.play(ObjectAnimator.ofFloat(this, View.ALPHA, 0.0f, 1.0f)) - .with(ObjectAnimator.ofFloat(this, View.TRANSLATION_X, 100f, 0)); - - set.play(exitAnim).with(enterAnim); - - return set; +public class CustomAnimationHandler implements AnimationHandler { + @Nullable TraversalAnimation createAnimation(@NonNull View from, @NonNull View to, + @TraversingOperation int operation) { + boolean forward = operation != TraversingOperation.POP; + + AnimatorSet set = new AnimatorSet(); + + set.setInterpolator(new DecelerateInterpolator()); + + final int width = from.getWidth(); + + if (forward) { + to.setTranslationX(width); + set.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, 0 - (width / 3))); + set.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, 0)); + } else { + to.setTranslationX(0 - (width / 3)); + set.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, width)); + set.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, 0)); + } + + return TraversalAnimation.newInstance(set, + forward ? TraversalAnimation.ABOVE : TraversalAnimation.BELOW); + } } } ``` diff --git a/app/build.gradle b/app/build.gradle index 2c8b356..c54193f 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,6 +42,4 @@ dependencies { compile "com.jakewharton.rxbinding:rxbinding-appcompat-v7:${rxbinding_lib_version}" compile "com.jakewharton.rxbinding:rxbinding-design:${rxbinding_lib_version}" compile "com.jakewharton.rxbinding:rxbinding-recyclerview-v7:${rxbinding_lib_version}" - compile "com.github.frankiesardo:auto-parcel:${auto_parcel_lib_version}" - apt "com.github.frankiesardo:auto-parcel-processor:${auto_parcel_lib_version}" } diff --git a/app/src/main/java/com/solera/defragsample/BreakdownPresenter.java b/app/src/main/java/com/solera/defragsample/BreakdownPresenter.java index df7c016..be39138 100755 --- a/app/src/main/java/com/solera/defragsample/BreakdownPresenter.java +++ b/app/src/main/java/com/solera/defragsample/BreakdownPresenter.java @@ -17,17 +17,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; - import com.solera.defrag.ViewStack; public class BreakdownPresenter extends Presenter { private static final String TOTAL_COST = "totalCost"; private static final String TOTAL_PEOPLE = "totalPeople"; - interface View extends PresenterView { - void setUi(@NonNull String totalCost, @NonNull String totalPeople, @NonNull String perPerson); - } - public static void push(@NonNull ViewStack viewStack, int totalCost, int totalPeople) { final Bundle parameters = new Bundle(); parameters.putInt(TOTAL_COST, totalCost); @@ -35,8 +30,7 @@ public static void push(@NonNull ViewStack viewStack, int totalCost, int totalPe viewStack.pushWithParameters(R.layout.breakdown, parameters); } - @Override - protected void onTakeView() { + @Override protected void onTakeView() { super.onTakeView(); final ViewStack viewStack = ViewStackHelper.getViewStack(getContext()); @@ -49,8 +43,11 @@ protected void onTakeView() { final int totalPeople = parameters.getInt(TOTAL_PEOPLE); final int result = totalCost / totalPeople; - getView().setUi(Integer.toString(totalCost), - Integer.toString(totalPeople), + getView().setUi(Integer.toString(totalCost), Integer.toString(totalPeople), Integer.toString(result)); } + + interface View extends PresenterView { + void setUi(@NonNull String totalCost, @NonNull String totalPeople, @NonNull String perPerson); + } } diff --git a/app/src/main/java/com/solera/defragsample/BreakdownView.java b/app/src/main/java/com/solera/defragsample/BreakdownView.java index e05d198..02dcef8 100755 --- a/app/src/main/java/com/solera/defragsample/BreakdownView.java +++ b/app/src/main/java/com/solera/defragsample/BreakdownView.java @@ -20,39 +20,32 @@ import android.util.AttributeSet; import android.widget.FrameLayout; import android.widget.TextView; - import butterknife.Bind; import butterknife.ButterKnife; public class BreakdownView extends FrameLayout implements BreakdownPresenter.View { private final BreakdownPresenter mPresenter = new BreakdownPresenter(); - @Bind(R.id.textview_costvalue) - TextView costTextView; - @Bind(R.id.textview_peoplevalue) - TextView peopleTextView; - @Bind(R.id.textview_perpersonvalue) - TextView perPersonTextView; + @Bind(R.id.textview_costvalue) TextView costTextView; + @Bind(R.id.textview_peoplevalue) TextView peopleTextView; + @Bind(R.id.textview_perpersonvalue) TextView perPersonTextView; public BreakdownView(Context context, AttributeSet attrs) { super(context, attrs); } - @Override - public void setUi(@NonNull String totalCost, @NonNull String totalPeople, - @NonNull String perPerson) { + @Override public void setUi(@NonNull String totalCost, @NonNull String totalPeople, + @NonNull String perPerson) { costTextView.setText(totalCost); peopleTextView.setText(totalPeople); perPersonTextView.setText(perPerson); } - @Override - protected void onFinishInflate() { + @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); } - @Override - protected void onAttachedToWindow() { + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (isInEditMode()) { return; @@ -60,8 +53,7 @@ protected void onAttachedToWindow() { mPresenter.takeView(this); } - @Override - protected void onDetachedFromWindow() { + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mPresenter.dropView(); } diff --git a/app/src/main/java/com/solera/defragsample/MainActivity.java b/app/src/main/java/com/solera/defragsample/MainActivity.java index 70afd8d..cb87316 100755 --- a/app/src/main/java/com/solera/defragsample/MainActivity.java +++ b/app/src/main/java/com/solera/defragsample/MainActivity.java @@ -15,56 +15,58 @@ */ package com.solera.defragsample; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; - +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import butterknife.Bind; +import butterknife.ButterKnife; +import com.solera.defrag.AnimationHandler; +import com.solera.defrag.TraversalAnimation; +import com.solera.defrag.TraversingOperation; import com.solera.defrag.TraversingState; import com.solera.defrag.ViewStack; import com.solera.defrag.ViewStackListener; -import butterknife.Bind; -import butterknife.ButterKnife; - public class MainActivity extends AppCompatActivity { - @Bind(R.id.viewstack) - ViewStack viewStack; + @Bind(R.id.viewstack) ViewStack viewStack; private boolean disableUI = false; - @Override - public void onBackPressed() { + @Override public void onBackPressed() { if (disableUI) { return; } - if (!viewStack.onBackPressed()) { + if (!viewStack.pop()) { super.onBackPressed(); } } - @Override - public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { + @Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { return disableUI || super.dispatchTouchEvent(ev); } - @Override - public Object getSystemService(@NonNull String name) { + @Override public Object getSystemService(@NonNull String name) { if (ViewStackHelper.matchesServiceName(name)) { return viewStack; } return super.getSystemService(name); } - @Override - protected void onCreate(Bundle savedInstanceState) { + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); + viewStack.setAnimationHandler(createCustomAnimationHandler()); + viewStack.addTraversingListener(new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + @Override public void onTraversing(@TraversingState int traversingState) { disableUI = traversingState != TraversingState.IDLE; } }); @@ -73,4 +75,33 @@ public void onTraversing(@NonNull TraversingState traversingState) { viewStack.push(R.layout.totalcost); } } + + @NonNull private AnimationHandler createCustomAnimationHandler() { + return new AnimationHandler() { + @Nullable @Override + public TraversalAnimation createAnimation(@NonNull View from, @NonNull View to, + @TraversingOperation int operation) { + boolean forward = operation != TraversingOperation.POP; + + AnimatorSet set = new AnimatorSet(); + + set.setInterpolator(new DecelerateInterpolator()); + + final int width = from.getWidth(); + + if (forward) { + to.setTranslationX(width); + set.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, 0 - (width / 3))); + set.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, 0)); + } else { + to.setTranslationX(0 - (width / 3)); + set.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, width)); + set.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, 0)); + } + + return TraversalAnimation.newInstance(set, + forward ? TraversalAnimation.ABOVE : TraversalAnimation.BELOW); + } + }; + } } diff --git a/app/src/main/java/com/solera/defragsample/Presenter.java b/app/src/main/java/com/solera/defragsample/Presenter.java index 2aa0134..724e675 100755 --- a/app/src/main/java/com/solera/defragsample/Presenter.java +++ b/app/src/main/java/com/solera/defragsample/Presenter.java @@ -17,7 +17,6 @@ import android.content.Context; import android.support.annotation.NonNull; - import rx.Subscription; import rx.exceptions.OnErrorNotImplementedException; import rx.functions.Action1; @@ -83,12 +82,9 @@ protected final T getView() { */ protected Action1 getDefaultErrorAction() { return new Action1() { - @Override - public void call(Throwable throwable) { + @Override public void call(Throwable throwable) { throw new OnErrorNotImplementedException("RxError source", throwable); } }; - } - } diff --git a/app/src/main/java/com/solera/defragsample/PresenterView.java b/app/src/main/java/com/solera/defragsample/PresenterView.java index 2d7baa7..66b34e5 100755 --- a/app/src/main/java/com/solera/defragsample/PresenterView.java +++ b/app/src/main/java/com/solera/defragsample/PresenterView.java @@ -22,6 +22,5 @@ * Based presenter view interface. */ public interface PresenterView { - @NonNull - Context getContext(); + @NonNull Context getContext(); } diff --git a/app/src/main/java/com/solera/defragsample/TotalCostPresenter.java b/app/src/main/java/com/solera/defragsample/TotalCostPresenter.java index 8cf841e..bc7f3ca 100755 --- a/app/src/main/java/com/solera/defragsample/TotalCostPresenter.java +++ b/app/src/main/java/com/solera/defragsample/TotalCostPresenter.java @@ -16,68 +16,56 @@ package com.solera.defragsample; import android.support.annotation.NonNull; - import rx.Observable; import rx.Subscription; import rx.functions.Action1; import rx.functions.Func1; public class TotalCostPresenter extends Presenter { - interface View extends PresenterView { - @NonNull - Observable onTotalCostChanged(); - - @NonNull - Observable onSubmit(); - - void enableSubmit(boolean enable); - - void showTotalPeople(int totalCost); - } - - @Override - protected void onTakeView() { + @Override protected void onTakeView() { super.onTakeView(); addViewSubscription(onSubmit()); addViewSubscription(onTotalCostChanged()); } - @NonNull - private Subscription onSubmit() { + @NonNull private Subscription onSubmit() { final View view = getView(); return view.onSubmit().flatMap(new Func1>() { - @Override - public Observable call(Object ignore) { + @Override public Observable call(Object ignore) { return view.onTotalCostChanged().map(new Func1() { - @Override - public Integer call(CharSequence charSequence) { + @Override public Integer call(CharSequence charSequence) { return Integer.parseInt(charSequence.toString()); } }); } }).first().subscribe(new Action1() { - @Override - public void call(Integer totalCost) { + @Override public void call(Integer totalCost) { view.showTotalPeople(totalCost); } }); } - @NonNull - private Subscription onTotalCostChanged() { + @NonNull private Subscription onTotalCostChanged() { final View view = getView(); return view.onTotalCostChanged().map(new Func1() { - @Override - public Boolean call(CharSequence charSequence) { + @Override public Boolean call(CharSequence charSequence) { return charSequence.length() != 0; } }).distinctUntilChanged().subscribe(new Action1() { - @Override - public void call(Boolean isValid) { + @Override public void call(Boolean isValid) { view.enableSubmit(isValid); } }, getDefaultErrorAction()); } + interface View extends PresenterView { + @NonNull Observable onTotalCostChanged(); + + @NonNull Observable onSubmit(); + + void enableSubmit(boolean enable); + + void showTotalPeople(int totalCost); + } } diff --git a/app/src/main/java/com/solera/defragsample/TotalCostView.java b/app/src/main/java/com/solera/defragsample/TotalCostView.java index d9c2105..668b66d 100755 --- a/app/src/main/java/com/solera/defragsample/TotalCostView.java +++ b/app/src/main/java/com/solera/defragsample/TotalCostView.java @@ -21,58 +21,46 @@ import android.util.AttributeSet; import android.widget.EditText; import android.widget.FrameLayout; - -import com.jakewharton.rxbinding.view.RxView; -import com.jakewharton.rxbinding.widget.RxTextView; - import butterknife.Bind; import butterknife.ButterKnife; +import com.jakewharton.rxbinding.view.RxView; +import com.jakewharton.rxbinding.widget.RxTextView; import rx.Observable; public class TotalCostView extends FrameLayout implements TotalCostPresenter.View { private final TotalCostPresenter presenter = new TotalCostPresenter(); - @Bind(R.id.button) - FloatingActionButton floatingActionButton; - @Bind(R.id.edittext) - EditText editText; + @Bind(R.id.button) FloatingActionButton floatingActionButton; + @Bind(R.id.edittext) EditText editText; public TotalCostView(Context context, AttributeSet attrs) { super(context, attrs); } - @NonNull - @Override - public Observable onTotalCostChanged() { + @NonNull @Override public Observable onTotalCostChanged() { return RxTextView.textChanges(editText); } - @NonNull - @Override - public Observable onSubmit() { + @NonNull @Override public Observable onSubmit() { return Observable.merge(RxView.clicks(floatingActionButton), RxTextView.editorActions(editText)); } - @Override - public void enableSubmit(boolean enable) { + @Override public void enableSubmit(boolean enable) { floatingActionButton.setEnabled(enable); final float scaleTo = enable ? 1.0f : 0.0f; floatingActionButton.animate().scaleX(scaleTo).scaleY(scaleTo); } - @Override - public void showTotalPeople(int totalCost) { + @Override public void showTotalPeople(int totalCost) { TotalPeoplePresenter.push(ViewStackHelper.getViewStack(this), totalCost); } - @Override - protected void onFinishInflate() { + @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); } - @Override - protected void onAttachedToWindow() { + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (isInEditMode()) { return; @@ -80,8 +68,7 @@ protected void onAttachedToWindow() { presenter.takeView(this); } - @Override - protected void onDetachedFromWindow() { + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); presenter.dropView(); } diff --git a/app/src/main/java/com/solera/defragsample/TotalPeoplePresenter.java b/app/src/main/java/com/solera/defragsample/TotalPeoplePresenter.java index 63da821..55407b3 100755 --- a/app/src/main/java/com/solera/defragsample/TotalPeoplePresenter.java +++ b/app/src/main/java/com/solera/defragsample/TotalPeoplePresenter.java @@ -16,64 +16,60 @@ package com.solera.defragsample; import android.support.annotation.NonNull; - import com.solera.defrag.ViewStack; - import rx.Observable; import rx.Subscription; import rx.functions.Action1; import rx.functions.Func1; public class TotalPeoplePresenter extends Presenter { - public interface View extends PresenterView { - @NonNull - Observable onTotalPeopleChanged(); + public static void push(@NonNull ViewStack viewStack, int totalCost) { + viewStack.pushWithParameter(R.layout.totalpeople, totalCost); + } - @NonNull - Observable onSubmit(); + @Override protected void onTakeView() { + super.onTakeView(); - void enableSubmit(boolean enable); + final Integer totalCost = ViewStackHelper.getViewStack(getContext()).getParameter(getView()); + if (totalCost == null) { + throw new IllegalStateException("Parameter is null"); + } - void showBreakdown(int totalCost, int totalPeople); - } + addViewSubscription(onSubmit(totalCost)); + addViewSubscription(onTotalPeopleChanged()); + } - public static void push(@NonNull ViewStack viewStack, int totalCost) { - viewStack.pushWithParameter(R.layout.totalpeople, totalCost); - } + @NonNull private Subscription onSubmit(final int totalCost) { + return getView().onSubmit().flatMap(new Func1>() { + @Override public Observable call(Object ignore) { + return getView().onTotalPeopleChanged(); + } + }).subscribe(new Action1() { + @Override public void call(Integer totalPeople) { + getView().showBreakdown(totalCost, totalPeople); + } + }, getDefaultErrorAction()); + } - @Override protected void onTakeView() { - super.onTakeView(); + @NonNull private Subscription onTotalPeopleChanged() { + return getView().onTotalPeopleChanged().map(new Func1() { + @Override public Boolean call(Integer integer) { + return integer != 0; + } + }).distinctUntilChanged().subscribe(new Action1() { + @Override public void call(Boolean isValid) { + getView().enableSubmit(isValid); + } + }, getDefaultErrorAction()); + } - final Integer totalCost = ViewStackHelper.getViewStack(getContext()).getParameter(getView()); - if (totalCost == null) { - throw new IllegalStateException("Parameter is null"); - } + public interface View extends PresenterView { + @NonNull Observable onTotalPeopleChanged(); - addViewSubscription(onSubmit(totalCost)); - addViewSubscription(onTotalPeopleChanged()); - } + @NonNull Observable onSubmit(); - @NonNull private Subscription onSubmit(final int totalCost) { - return getView().onSubmit().flatMap(new Func1>() { - @Override public Observable call(Object ignore) { - return getView().onTotalPeopleChanged(); - } - }).subscribe(new Action1() { - @Override public void call(Integer totalPeople) { - getView().showBreakdown(totalCost,totalPeople); - } - }, getDefaultErrorAction()); - } + void enableSubmit(boolean enable); - @NonNull private Subscription onTotalPeopleChanged() { - return getView().onTotalPeopleChanged().map(new Func1() { - @Override public Boolean call(Integer integer) { - return integer != 0; - } - }).distinctUntilChanged().subscribe(new Action1() { - @Override public void call(Boolean isValid) { - getView().enableSubmit(isValid); - } - }, getDefaultErrorAction()); - } + void showBreakdown(int totalCost, int totalPeople); + } } diff --git a/app/src/main/java/com/solera/defragsample/TotalPeopleView.java b/app/src/main/java/com/solera/defragsample/TotalPeopleView.java index 1a7d570..fdbff91 100755 --- a/app/src/main/java/com/solera/defragsample/TotalPeopleView.java +++ b/app/src/main/java/com/solera/defragsample/TotalPeopleView.java @@ -22,72 +22,57 @@ import android.util.AttributeSet; import android.widget.FrameLayout; import android.widget.TextView; - -import com.jakewharton.rxbinding.view.RxView; -import com.jakewharton.rxbinding.widget.RxSeekBar; - import butterknife.Bind; import butterknife.ButterKnife; +import com.jakewharton.rxbinding.view.RxView; +import com.jakewharton.rxbinding.widget.RxSeekBar; import rx.Observable; import rx.functions.Action1; import rx.functions.Func1; public class TotalPeopleView extends FrameLayout implements TotalPeoplePresenter.View { private final TotalPeoplePresenter presenter = new TotalPeoplePresenter(); - @Bind(R.id.button) - FloatingActionButton floatingActionButton; - @Bind(R.id.seekbar) - AppCompatSeekBar seekBar; - @Bind(R.id.textview_number) - TextView textView; + @Bind(R.id.button) FloatingActionButton floatingActionButton; + @Bind(R.id.seekbar) AppCompatSeekBar seekBar; + @Bind(R.id.textview_number) TextView textView; public TotalPeopleView(Context context, AttributeSet attrs) { super(context, attrs); } - @NonNull - @Override - public Observable onTotalPeopleChanged() { + @NonNull @Override public Observable onTotalPeopleChanged() { return RxSeekBar.changes(seekBar).map(new Func1() { - @Override - public Integer call(Integer integer) { + @Override public Integer call(Integer integer) { return integer / 12; } }).doOnNext(new Action1() { - @Override - public void call(Integer integer) { + @Override public void call(Integer integer) { textView.setText(Integer.toString(integer)); } }); } - @NonNull - @Override - public Observable onSubmit() { + @NonNull @Override public Observable onSubmit() { return RxView.clicks(floatingActionButton); } - @Override - public void enableSubmit(boolean enable) { + @Override public void enableSubmit(boolean enable) { floatingActionButton.setEnabled(enable); final float scaleTo = enable ? 1.0f : 0.0f; floatingActionButton.animate().scaleX(scaleTo).scaleY(scaleTo); textView.animate().scaleX(scaleTo).scaleY(scaleTo); } - @Override - public void showBreakdown(int totalCost, int totalPeople) { + @Override public void showBreakdown(int totalCost, int totalPeople) { BreakdownPresenter.push(ViewStackHelper.getViewStack(this), totalCost, totalPeople); } - @Override - protected void onFinishInflate() { + @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); } - @Override - protected void onAttachedToWindow() { + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (isInEditMode()) { return; @@ -95,8 +80,7 @@ protected void onAttachedToWindow() { presenter.takeView(this); } - @Override - protected void onDetachedFromWindow() { + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); presenter.dropView(); } diff --git a/app/src/main/java/com/solera/defragsample/ViewStackHelper.java b/app/src/main/java/com/solera/defragsample/ViewStackHelper.java index 42aad04..2d47a16 100755 --- a/app/src/main/java/com/solera/defragsample/ViewStackHelper.java +++ b/app/src/main/java/com/solera/defragsample/ViewStackHelper.java @@ -4,7 +4,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; - import com.solera.defrag.ViewStack; /** @@ -16,13 +15,11 @@ public class ViewStackHelper { private ViewStackHelper() { } - @Nullable - public static ViewStack getViewStack(@NonNull View view) { + @Nullable public static ViewStack getViewStack(@NonNull View view) { return getViewStack(view.getContext()); } - @Nullable - public static ViewStack getViewStack(@NonNull Context context) { + @Nullable public static ViewStack getViewStack(@NonNull Context context) { //noinspection WrongConstant return (ViewStack) context.getSystemService(VIEW_STACK_SERVICE_NAME); } diff --git a/build.gradle b/build.gradle index 234a6bc..b23bf23 100755 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.1' + classpath 'com.android.tools.build:gradle:2.2.2' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' diff --git a/defrag/build.gradle b/defrag/build.gradle index a66a79d..13b9f61 100755 --- a/defrag/build.gradle +++ b/defrag/build.gradle @@ -4,36 +4,38 @@ apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'android-apt' group = 'com.solera.defrag' -version = '1.0.0' +version = '1.1.0' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" - - defaultConfig { - minSdkVersion 16 - targetSdkVersion 23 - versionCode 1 - versionName "${version}" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + compileSdkVersion 23 + buildToolsVersion "23.0.3" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 23 + versionCode 2 + versionName "${version}" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } } - } } dependencies { - compile fileTree(dir: 'libs', include: ['*.aar']) - testCompile 'junit:junit:4.12' + compile fileTree(dir: 'libs', include: ['*.aar']) + testCompile 'junit:junit:4.12' - compile "com.android.support:appcompat-v7:${android_support_lib_version}" - compile "com.android.support:support-annotations:${android_support_lib_version}" + compile "com.android.support:appcompat-v7:${android_support_lib_version}" + compile "com.android.support:support-annotations:${android_support_lib_version}" - compile "com.github.frankiesardo:auto-parcel:${auto_parcel_lib_version}" - apt "com.github.frankiesardo:auto-parcel-processor:${auto_parcel_lib_version}" + // Auto value + compile "com.google.auto.value:auto-value:${auto_value_lib_version}" + apt "com.google.auto.value:auto-value:${auto_value_lib_version}" + apt "com.ryanharter.auto.value:auto-value-parcel:0.2.5" } def siteUrl = 'https://github.com/R3PI/Defrag' @@ -41,76 +43,76 @@ def gitUrl = 'https://github.com/R3PI/Defrag.git' bintray { - //Initialise bintray user/api_key from local.properties file - Properties properties = new Properties() - properties.load(project.rootProject.file('local.properties').newDataInputStream()) - user = properties.getProperty('bintray.user') - key = properties.getProperty('bintray.apikey') - - configurations = ['archives'] //When uploading configuration files - pkg { - repo = 'Maven' - name = 'Defrag' - desc = 'Defrag is a simple to use library that allows for Fragment-free Android applications' - websiteUrl = siteUrl - issueTrackerUrl = 'https://github.com/R3PI/Defrag/issues' - vcsUrl = gitUrl - licenses = ['Apache-2.0'] - labels = ['aar', 'android'] - publicDownloadNumbers = false - } + //Initialise bintray user/api_key from local.properties file + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + user = properties.getProperty('bintray.user') + key = properties.getProperty('bintray.apikey') + + configurations = ['archives'] //When uploading configuration files + pkg { + repo = 'Maven' + name = 'Defrag' + desc = 'Defrag is a simple to use library that allows for Fragment-free Android applications' + websiteUrl = siteUrl + issueTrackerUrl = 'https://github.com/R3PI/Defrag/issues' + vcsUrl = gitUrl + licenses = ['Apache-2.0'] + labels = ['aar', 'android'] + publicDownloadNumbers = false + } } install { - repositories.mavenInstaller { - pom { - project { - packaging 'aar' - name 'Defrag description' - url siteUrl - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id 'tomjhall' - name 'Tom Hall' - email 'tom_j_hall@hotmail.com' - } - } - scm { - connection 'https://github.com/R3PI/Defrag.git' - developerConnection 'https://github.com/R3PI/Defrag.git' - url siteUrl + repositories.mavenInstaller { + pom { + project { + packaging 'aar' + name 'Defrag description' + url siteUrl + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + developers { + developer { + id 'tomjhall' + name 'Tom Hall' + email 'tom_j_hall@hotmail.com' + } + } + scm { + connection 'https://github.com/R3PI/Defrag.git' + developerConnection 'https://github.com/R3PI/Defrag.git' + url siteUrl + } + } } - } } - } } task sourcesJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' + from android.sourceSets.main.java.srcDirs + classifier = 'sources' } task javadoc(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir + classifier = 'javadoc' + from javadoc.destinationDir } artifacts { - archives javadocJar - archives sourcesJar + archives javadocJar + archives sourcesJar } task findConventions << { - println project.getConvention() + println project.getConvention() } diff --git a/defrag/src/main/java/com/solera/defrag/AnimationHandler.java b/defrag/src/main/java/com/solera/defrag/AnimationHandler.java new file mode 100644 index 0000000..ec5a39a --- /dev/null +++ b/defrag/src/main/java/com/solera/defrag/AnimationHandler.java @@ -0,0 +1,18 @@ +package com.solera.defrag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +public interface AnimationHandler { + /** + * Creates a TraversalAnimation for the given views. + * + * @param from the view that is transitioning out. + * @param to the view that is transitioning in. + * @param operation the type of operation. + * @return the animation, or null if there is no animation. + */ + @Nullable TraversalAnimation createAnimation(@NonNull View from, @NonNull View to, + @TraversingOperation int operation); +} diff --git a/defrag/src/main/java/com/solera/defrag/DefaultAnimationHandler.java b/defrag/src/main/java/com/solera/defrag/DefaultAnimationHandler.java new file mode 100644 index 0000000..f7ad8d9 --- /dev/null +++ b/defrag/src/main/java/com/solera/defrag/DefaultAnimationHandler.java @@ -0,0 +1,46 @@ +package com.solera.defrag; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.animation.OvershootInterpolator; + +/** + * An implementation of the AnimationHandler. A view bounces in/out when being added/removed + * from the stack. + */ +class DefaultAnimationHandler implements AnimationHandler { + private static final int DEFAULT_ANIMATION_DURATION_IN_MS = 300; + + @Nullable @Override + public TraversalAnimation createAnimation(@NonNull View from, @NonNull View to, + @TraversingOperation int operation) { + + boolean forward = operation != TraversingOperation.POP; + + AnimatorSet set = new AnimatorSet(); + + set.setInterpolator(new OvershootInterpolator()); + set.setDuration(DEFAULT_ANIMATION_DURATION_IN_MS); + + set.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0.0f)); + set.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0.5f, 1.0f)); + + if (forward) { + set.play(ObjectAnimator.ofFloat(from, View.SCALE_X, 0.9f)); + set.play(ObjectAnimator.ofFloat(from, View.SCALE_Y, 0.9f)); + set.play(ObjectAnimator.ofFloat(to, View.SCALE_X, 1.1f, 1.0f)); + set.play(ObjectAnimator.ofFloat(to, View.SCALE_Y, 1.1f, 1.0f)); + } else { + set.play(ObjectAnimator.ofFloat(from, View.SCALE_X, 1.1f)); + set.play(ObjectAnimator.ofFloat(from, View.SCALE_Y, 1.1f)); + set.play(ObjectAnimator.ofFloat(to, View.SCALE_X, 0.9f, 1.0f)); + set.play(ObjectAnimator.ofFloat(to, View.SCALE_Y, 0.9f, 1.0f)); + } + + return TraversalAnimation.newInstance(set, + forward ? TraversalAnimation.BELOW : TraversalAnimation.ABOVE); + } +} diff --git a/defrag/src/main/java/com/solera/defrag/HandlesBackPresses.java b/defrag/src/main/java/com/solera/defrag/HandlesBackPresses.java index 755fc8f..25eca1b 100755 --- a/defrag/src/main/java/com/solera/defrag/HandlesBackPresses.java +++ b/defrag/src/main/java/com/solera/defrag/HandlesBackPresses.java @@ -19,13 +19,15 @@ /** * A view that handles back press events. If a view on the {@link ViewStack} implements this, the * event will be handled here, rather than the default behaviour ({@link ViewStack#pop()}. + * + * @deprecated call {@link ViewStack#pop()} directly when receiving the back press event. */ -public interface HandlesBackPresses { - /** - * Handle a back press event. - * - * @return true if the view handled this event, false otherwise. If the event is not handled here, - * the activity should handle it. - */ - boolean onBackPressed(); +@Deprecated public interface HandlesBackPresses { + /** + * Handle a back press event. + * + * @return true if the view handled this event, false otherwise. If the event is not handled here, + * the activity should handle it. + */ + boolean onBackPressed(); } diff --git a/defrag/src/main/java/com/solera/defrag/HasTraversalAnimation.java b/defrag/src/main/java/com/solera/defrag/HasTraversalAnimation.java index e43990f..11e6bcd 100755 --- a/defrag/src/main/java/com/solera/defrag/HasTraversalAnimation.java +++ b/defrag/src/main/java/com/solera/defrag/HasTraversalAnimation.java @@ -15,19 +15,20 @@ */ package com.solera.defrag; -import android.animation.Animator; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; /** - * A view that defines it's own traversal animation. If a view on the {@link ViewStack} implements - * this, the animation returned by createAnimation will be run rather than the default. + * A view that defines it's own traversal animator. If a view on the {@link ViewStack} implements + * this, the animator returned by createAnimation will be run rather than the default. + * + * @deprecated use {@link AnimationHandler} to handle traversal animations. */ -public interface HasTraversalAnimation { - /** - * @param fromView the view that we are traversing from. - * @return an animation that will be run for the traversal, or null if the default should be run. - */ - @Nullable Animator createAnimation(@NonNull View fromView); +@Deprecated public interface HasTraversalAnimation { + /** + * @param fromView the view that we are traversing from. + * @return an animator that will be run for the traversal, or null if the default should be run. + */ + @Nullable TraversalAnimation createAnimation(@NonNull View fromView); } diff --git a/defrag/src/main/java/com/solera/defrag/TraversalAnimation.java b/defrag/src/main/java/com/solera/defrag/TraversalAnimation.java new file mode 100644 index 0000000..9d95807 --- /dev/null +++ b/defrag/src/main/java/com/solera/defrag/TraversalAnimation.java @@ -0,0 +1,26 @@ +package com.solera.defrag; + +import android.animation.Animator; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import com.google.auto.value.AutoValue; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@AutoValue public abstract class TraversalAnimation { + public static final int ABOVE = 0; + public static final int BELOW = 1; + + @NonNull public static TraversalAnimation newInstance(@NonNull Animator animator, + @AnimateInDrawOrder int drawOrder) { + return new AutoValue_TraversalAnimation(animator, drawOrder); + } + + @NonNull abstract Animator animator(); + + @AnimateInDrawOrder abstract int drawOrder(); + + @Retention(SOURCE) @IntDef({ ABOVE, BELOW }) public @interface AnimateInDrawOrder { + } +} diff --git a/defrag/src/main/java/com/solera/defrag/ViewStackEmptyListener.java b/defrag/src/main/java/com/solera/defrag/TraversingOperation.java similarity index 64% rename from defrag/src/main/java/com/solera/defrag/ViewStackEmptyListener.java rename to defrag/src/main/java/com/solera/defrag/TraversingOperation.java index c9cac9a..c7089e1 100755 --- a/defrag/src/main/java/com/solera/defrag/ViewStackEmptyListener.java +++ b/defrag/src/main/java/com/solera/defrag/TraversingOperation.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.solera.defrag; -/** - * Interface definition of a callback for when a pop has been called on a stack of one. - */ -public interface ViewStackEmptyListener { - /** - * Called when a pop has been called on a stack containing one. - */ - void onPopToEmpty(); +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@Retention(SOURCE) +@IntDef({ TraversingOperation.PUSH, TraversingOperation.POP, TraversingOperation.REPLACE }) +public @interface TraversingOperation { + int PUSH = 1; + int POP = 2; + int REPLACE = 3; } diff --git a/defrag/src/main/java/com/solera/defrag/TraversingState.java b/defrag/src/main/java/com/solera/defrag/TraversingState.java index 734f16c..77fdaf3 100755 --- a/defrag/src/main/java/com/solera/defrag/TraversingState.java +++ b/defrag/src/main/java/com/solera/defrag/TraversingState.java @@ -15,9 +15,17 @@ */ package com.solera.defrag; -public enum TraversingState { - IDLE, - PUSHING, - POPPING, - REPLACING +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@Retention(SOURCE) @IntDef({ + TraversingState.IDLE, TraversingState.PUSHING, TraversingState.POPPING, + TraversingState.REPLACING +}) public @interface TraversingState { + int IDLE = 0; + int PUSHING = 1; + int POPPING = 2; + int REPLACING = 3; } diff --git a/defrag/src/main/java/com/solera/defrag/ViewStack.java b/defrag/src/main/java/com/solera/defrag/ViewStack.java index 8f5d560..2ffd091 100755 --- a/defrag/src/main/java/com/solera/defrag/ViewStack.java +++ b/defrag/src/main/java/com/solera/defrag/ViewStack.java @@ -17,8 +17,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; @@ -33,32 +31,30 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; - +import com.google.auto.value.AutoValue; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import auto.parcel.AutoParcel; - /** * Handles a stack of views, and animations between these views. */ public class ViewStack extends FrameLayout { //Explicitly create a new string - as we use this reference as a token public static final Bundle USE_EXISTING_SAVED_STATE = new Bundle(); - private static final int DEFAULT_ANIMATION_DURATION_IN_MS = 300; private static final String SINGLE_PARAMETER_KEY = "view_stack_single_param"; + final Deque viewStack = new ArrayDeque<>(); private final Collection viewStackListeners = new CopyOnWriteArrayList<>(); - private final Deque viewStack = new ArrayDeque<>(); - private TraversingState traversingState = TraversingState.IDLE; + @TraversingState private int traversingState = TraversingState.IDLE; + private AnimationHandler animationHandler = new DefaultAnimationHandler(); private Object result; public ViewStack(Context context) { @@ -78,14 +74,20 @@ public ViewStack(Context context, AttributeSet attrs, int defStyleAttr, int defS super(context, attrs, defStyleAttr, defStyleRes); } + @NonNull public AnimationHandler setAnimationHandler(@NonNull AnimationHandler handler) { + final AnimationHandler oldHandler = this.animationHandler; + animationHandler = handler; + return oldHandler; + } + /** - * It should be called in the {@link Activity#onBackPressed()} in order to handle the backpress + * It should be called in the {@link Activity#onBackPressed()} in order to handle the back press * events correctly. * - * @return true if the back press event was handled by the viewstack, false otherwise (and so the + * @return true if the back press event was handled by the ViewStack, false otherwise (and so the * activity should handle this event). */ - public boolean onBackPressed() { + @Deprecated public boolean onBackPressed() { final View topView = getTopView(); if (topView != null && topView instanceof HandlesBackPresses) { return ((HandlesBackPresses) topView).onBackPressed(); @@ -93,8 +95,7 @@ public boolean onBackPressed() { return pop(); } - @Nullable - public View getTopView() { + @Nullable public View getTopView() { final ViewStackEntry peek = viewStack.peek(); if (peek != null) { return peek.getView(); @@ -102,6 +103,11 @@ public View getTopView() { return null; } + /** + * Pops the top view from the stack. + * + * @return true if the operation succeeded, or false if there was no view. + */ public boolean pop() { return popWithResult(1, null); } @@ -121,9 +127,8 @@ public boolean popWithResult(int count, @Nullable Object result) { addView(toView); peek.restoreState(toView); ViewUtils.waitForMeasure(toView, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { - ViewStack.this.runAnimation(fromView, toView, Direction.BACK); + @Override public void onMeasured(View view, int width, int height) { + ViewStack.this.runAnimation(fromView, toView, TraversingOperation.POP); } }); return true; @@ -139,12 +144,11 @@ public void removeTraversingListener(@NonNull ViewStackListener listener) { viewStackListeners.remove(listener); } - @NonNull - public TraversingState getTraversingState() { + @TraversingState public int getTraversingState() { return traversingState; } - private void setTraversingState(@NonNull TraversingState traversing) { + void setTraversingState(@TraversingState int traversing) { if (traversing != TraversingState.IDLE && traversingState != TraversingState.IDLE) { throw new IllegalStateException("ViewStack is currently traversing"); } @@ -173,21 +177,22 @@ public void replaceWithParameter(@LayoutRes int layout, @Nullable Serializable p } public void replaceWithParameters(@LayoutRes int layout, @Nullable Bundle parameters) { + // push layout instead of replacing it when view stack is empty + if (viewStack.isEmpty()) { + pushWithParameters(layout, parameters); + return; + } setTraversingState(TraversingState.REPLACING); final ViewStackEntry viewStackEntry = new ViewStackEntry(layout, parameters, null); final View view = viewStackEntry.getView(); - if (viewStack.isEmpty()) { - throw new IllegalStateException("Replace on an empty stack"); - } final ViewStackEntry topEntry = viewStack.peek(); final View fromView = topEntry.getView(); viewStack.push(viewStackEntry); addView(view); ViewUtils.waitForMeasure(view, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { - ViewStack.this.runAnimation(fromView, view, Direction.FORWARD); + @Override public void onMeasured(View view, int width, int height) { + ViewStack.this.runAnimation(fromView, view, TraversingOperation.REPLACE); viewStack.remove(topEntry); } }); @@ -214,8 +219,7 @@ public void pushWithParameters(@LayoutRes int layout, @Nullable Bundle parameter viewStack.push(viewStackEntry); addView(view); ViewUtils.waitForMeasure(view, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { + @Override public void onMeasured(View view, int width, int height) { setTraversingState(TraversingState.IDLE); } }); @@ -229,15 +233,14 @@ public void onMeasured(View view, int width, int height) { addView(view); ViewUtils.waitForMeasure(view, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { - ViewStack.this.runAnimation(fromView, view, Direction.FORWARD); + @Override public void onMeasured(View view, int width, int height) { + ViewStack.this.runAnimation(fromView, view, TraversingOperation.PUSH); } }); } /** - * Replace the current stack with the given views, if the Serializable component + * Replace the current stack with the given views, if the Bundle component * is the USE_EXISTING_SAVED_STATE tag, then we will use that saved state for that * view (if it exists, and is at the right location in the stack) otherwise this will be null. */ @@ -285,8 +288,7 @@ public void replaceStack(@NonNull List> views) { removeAllViews(); addView(toView); ViewUtils.waitForMeasure(toView, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { + @Override public void onMeasured(View view, int width, int height) { setTraversingState(TraversingState.IDLE); } }); @@ -295,21 +297,27 @@ public void onMeasured(View view, int width, int height) { addView(toView); ViewUtils.waitForMeasure(toView, new ViewUtils.OnMeasuredCallback() { - @Override - public void onMeasured(View view, int width, int height) { - ViewStack.this.runAnimation(fromView, toView, Direction.FORWARD); + @Override public void onMeasured(View view, int width, int height) { + ViewStack.this.runAnimation(fromView, toView, TraversingOperation.REPLACE); viewStack.remove(fromEntry); } }); } } + /** + * Replace the current stack with the given view, if the Bundle component + * is the USE_EXISTING_SAVED_STATE tag, then we will use that saved state for that + * view (if it exists, and is at the right location in the stack) otherwise this will be null. + */ + public void replaceStack(@LayoutRes Integer layout, @Nullable Bundle parameters) { + replaceStack(Collections.singletonList(Pair.create(layout, parameters))); + } + /** * @return the result (if any) of the last popped view, and clears this result. */ - @SuppressWarnings("unchecked") - @Nullable - public T getResult() { + @SuppressWarnings("unchecked") @Nullable public T getResult() { final T result = (T) this.result; this.result = null; return result; @@ -319,9 +327,8 @@ public T getResult() { * @param view the view to retrieve the parameters for. * @return the parameters, or null if none found. */ - @SuppressWarnings("unchecked") - @Nullable - public T getParameter(@NonNull Object view) { + @SuppressWarnings("unchecked") @Nullable public T getParameter( + @NonNull Object view) { final Bundle parameters = getParameters(view); if (parameters == null) { return null; @@ -331,7 +338,7 @@ public T getParameter(@NonNull Object view) { } /** - * @param view the view to set the parameter for. + * @param view the view to set the parameter for. * @param parameter the parameter to set. */ public void setParameter(@NonNull Object view, @Nullable Serializable parameter) { @@ -341,8 +348,7 @@ public void setParameter(@NonNull Object view, @Nullable Serializable parameter) /** * @return the start parameters of the view/presenter */ - @Nullable - public Bundle getParameters(@NonNull Object view) { + @Nullable public Bundle getParameters(@NonNull Object view) { final Iterator viewStackEntryIterator = viewStack.descendingIterator(); while (viewStackEntryIterator.hasNext()) { final ViewStackEntry viewStackEntry = viewStackEntryIterator.next(); @@ -398,17 +404,23 @@ public boolean pop(int count) { return popWithResult(count, null); } - @Override - protected Parcelable onSaveInstanceState() { + @Override protected int getChildDrawingOrder(int childCount, int index) { + //if this method gets called - always reverse the order + //There are at most 2 views in this ViewGroup + return index == 0 ? 1 : 0; + } + + @Override protected Parcelable onSaveInstanceState() { final Parcelable parcelable = super.onSaveInstanceState(); return SaveState.newInstance(this, parcelable); } - @Override - protected void onRestoreInstanceState(Parcelable state) { + @Override protected void onRestoreInstanceState(Parcelable state) { final SaveState parcelable = (SaveState) state; for (SaveStateEntry entry : parcelable.stack()) { - viewStack.add(new ViewStackEntry(entry.layout(), entry.parameters(), entry.viewState())); + //we have to cast to SparseArray as we can't serialize a SparseArray + viewStack.add( + new ViewStackEntry(entry.layout(), entry.parameters(), (SparseArray) entry.viewState())); } if (!viewStack.isEmpty()) { addView(viewStack.peek().getView()); @@ -416,62 +428,41 @@ protected void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(parcelable.superState()); } - private void runAnimation(final View from, final View to, - Direction direction) { - Animator animator = createAnimation(from, to, direction); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - removeView(from); - setTraversingState(TraversingState.IDLE); - } - }); - animator.start(); + void runAnimation(@NonNull final View from, @NonNull final View to, + @TraversingOperation int operation) { + final TraversalAnimation traversalAnimation = createAnimation(from, to, operation); + if (traversalAnimation == null) { + removeView(from); + setTraversingState(TraversingState.IDLE); + } else { + final Animator animator = traversalAnimation.animator(); + setChildrenDrawingOrderEnabled(traversalAnimation.drawOrder() == TraversalAnimation.BELOW); + animator.addListener(new AnimatorListenerAdapter() { + @Override public void onAnimationEnd(Animator animation) { + removeView(from); + setTraversingState(TraversingState.IDLE); + setChildrenDrawingOrderEnabled(false); + } + }); + animator.start(); + } } - @NonNull - private Animator createAnimation(@NonNull View from, @NonNull View to, - @NonNull Direction direction) { - Animator animation = null; + @Nullable private TraversalAnimation createAnimation(@NonNull View from, @NonNull View to, + @TraversingOperation int operation) { + TraversalAnimation animation = null; if (to instanceof HasTraversalAnimation) { animation = ((HasTraversalAnimation) to).createAnimation(from); } if (animation == null) { - return createDefaultAnimation(from, to, direction); + return animationHandler.createAnimation(from, to, operation); } else { return animation; } } - private Animator createDefaultAnimation(View from, View to, Direction direction) { - boolean backward = direction == Direction.BACK; - - AnimatorSet set = new AnimatorSet(); - - set.setInterpolator(new OvershootInterpolator()); - set.setDuration(DEFAULT_ANIMATION_DURATION_IN_MS); - - set.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0.0f)); - set.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0.5f, 1.0f)); - - if (backward) { - set.play(ObjectAnimator.ofFloat(from, View.SCALE_X, 1.1f)); - set.play(ObjectAnimator.ofFloat(from, View.SCALE_Y, 1.1f)); - set.play(ObjectAnimator.ofFloat(to, View.SCALE_X, 0.9f, 1.0f)); - set.play(ObjectAnimator.ofFloat(to, View.SCALE_Y, 0.9f, 1.0f)); - } else { - set.play(ObjectAnimator.ofFloat(from, View.SCALE_X, 0.9f)); - set.play(ObjectAnimator.ofFloat(from, View.SCALE_Y, 0.9f)); - set.play(ObjectAnimator.ofFloat(to, View.SCALE_X, 1.1f, 1.0f)); - set.play(ObjectAnimator.ofFloat(to, View.SCALE_Y, 1.1f, 1.0f)); - } - - return set; - } - - @Nullable - private Bundle createSimpleBundle(@Nullable Serializable parameter) { + @Nullable private Bundle createSimpleBundle(@Nullable Serializable parameter) { final Bundle parameterBundle; if (parameter == null) { parameterBundle = null; @@ -483,54 +474,42 @@ private Bundle createSimpleBundle(@Nullable Serializable parameter) { return parameterBundle; } - private enum Direction { - BACK, - FORWARD - } - - @AutoParcel - static abstract class SaveState implements Parcelable { + @AutoValue static abstract class SaveState implements Parcelable { static SaveState newInstance(@NonNull ViewStack viewstack, @NonNull Parcelable superState) { List stack = new ArrayList<>(viewstack.getViewCount()); for (ViewStackEntry entry : viewstack.viewStack) { stack.add(SaveStateEntry.newInstance(entry.mLayout, entry.mParameters, entry.mViewState)); } - return new AutoParcel_ViewStack_SaveState(stack, superState); + return new AutoValue_ViewStack_SaveState(stack, superState); } - @NonNull - abstract List stack(); + @NonNull abstract List stack(); - @NonNull - abstract Parcelable superState(); + @NonNull abstract Parcelable superState(); } - @AutoParcel - static abstract class SaveStateEntry implements Parcelable { - static SaveStateEntry newInstance(int layout, @Nullable Bundle parameters, @Nullable SparseArray viewState) { - return new AutoParcel_ViewStack_SaveStateEntry(layout, parameters, viewState); + @AutoValue static abstract class SaveStateEntry implements Parcelable { + static SaveStateEntry newInstance(int layout, @Nullable Bundle parameters, + @Nullable SparseArray viewState) { + return new AutoValue_ViewStack_SaveStateEntry(layout, parameters, (SparseArray) viewState); } - @LayoutRes - abstract int layout(); + @LayoutRes abstract int layout(); - @Nullable - abstract Bundle parameters(); + @Nullable abstract Bundle parameters(); - @Nullable - abstract SparseArray viewState(); + //Auto-value-parcel has a compilation error with SparseArray + @Nullable abstract SparseArray viewState(); } private class ViewStackEntry { - @LayoutRes - private final int mLayout; - @Nullable - private Bundle mParameters; - @Nullable - private SparseArray mViewState; - private WeakReference mViewReference = new WeakReference<>(null); - - ViewStackEntry(@LayoutRes int layout, @Nullable Bundle parameters, @Nullable SparseArray viewState) { + @LayoutRes final int mLayout; + @Nullable Bundle mParameters; + @Nullable SparseArray mViewState; + WeakReference mViewReference = new WeakReference<>(null); + + ViewStackEntry(@LayoutRes int layout, @Nullable Bundle parameters, + @Nullable SparseArray viewState) { mLayout = layout; mParameters = parameters; mViewState = viewState; @@ -540,19 +519,19 @@ void setParameters(@Nullable Bundle parameters) { mParameters = parameters; } - private void saveState(@NonNull View view) { + void saveState(@NonNull View view) { final SparseArray parcelableSparseArray = new SparseArray<>(); view.saveHierarchyState(parcelableSparseArray); mViewState = parcelableSparseArray; } - private void restoreState(@NonNull View view) { + void restoreState(@NonNull View view) { if (mViewState != null) { view.restoreHierarchyState(mViewState); } } - private View getView() { + @NonNull View getView() { View view = mViewReference.get(); if (view == null) { view = LayoutInflater.from(getContext()).inflate(mLayout, ViewStack.this, false); diff --git a/defrag/src/main/java/com/solera/defrag/ViewStackListener.java b/defrag/src/main/java/com/solera/defrag/ViewStackListener.java index cec13ff..17907cf 100755 --- a/defrag/src/main/java/com/solera/defrag/ViewStackListener.java +++ b/defrag/src/main/java/com/solera/defrag/ViewStackListener.java @@ -15,14 +15,12 @@ */ package com.solera.defrag; -import android.support.annotation.NonNull; - /** * Interface definition of a callback for when the traversal state has changed in the ViewStack. */ public interface ViewStackListener { - /** - * Called when the ViewStack is changing state. - */ - void onTraversing(@NonNull TraversingState traversingState); + /** + * Called when the ViewStack is changing state. + */ + void onTraversing(@TraversingState int traversingState); } diff --git a/defrag/src/main/java/com/solera/defrag/ViewStackUtils.java b/defrag/src/main/java/com/solera/defrag/ViewStackUtils.java index 85d1a4e..aed81d6 100644 --- a/defrag/src/main/java/com/solera/defrag/ViewStackUtils.java +++ b/defrag/src/main/java/com/solera/defrag/ViewStackUtils.java @@ -29,9 +29,8 @@ public static void safeReplace(@NonNull final ViewStack viewStack, @LayoutRes in */ public static void safeReplaceWithParameters(@NonNull final ViewStack viewStack, @LayoutRes final int layout, @Nullable final Bundle parameters) { - waitForTraversingState(viewStack, TraversingState.IDLE, new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { viewStack.replaceWithParameters(layout, parameters); } }); @@ -43,14 +42,26 @@ public void onTraversing(@NonNull TraversingState traversingState) { */ public static void safeReplaceStack(@NonNull final ViewStack viewStack, @NonNull final List> views) { - waitForTraversingState(viewStack, TraversingState.IDLE, new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { viewStack.replaceStack(views); } }); } + /** + * Safely replace the {@link ViewStack} stack as soon as the {@link ViewStack} will be in {@link + * TraversingState#IDLE}. If it is already in the idle state, method is invoked immediately. + */ + public static void safeReplaceStack(@NonNull final ViewStack viewStack, + @LayoutRes final Integer layout, @Nullable final Bundle parameters) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { + viewStack.replaceStack(layout, parameters); + } + }); + } + /** * Safely push a view (as soon as the {@link ViewStack} will be in {@link TraversingState#IDLE}, * if it is already in the idle state, method is invoked immediately). @@ -65,9 +76,8 @@ public static void safePush(@NonNull final ViewStack viewStack, @LayoutRes int l */ public static void safePushWithParameters(@NonNull final ViewStack viewStack, @LayoutRes final int layout, @Nullable final Bundle parameters) { - waitForTraversingState(viewStack, TraversingState.IDLE, new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { viewStack.pushWithParameters(layout, parameters); } }); @@ -91,9 +101,8 @@ public static void safePop(@NonNull final ViewStack viewStack) { */ public static void safePopWithResult(@NonNull final ViewStack viewStack, @Nullable final Object result) { - waitForTraversingState(viewStack, TraversingState.IDLE, new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { viewStack.popWithResult(result); } }); @@ -107,9 +116,8 @@ public void onTraversing(@NonNull TraversingState traversingState) { */ public static void safePopBackToWithResult(@NonNull final ViewStack viewStack, @LayoutRes final int layout, @Nullable final Object result) { - waitForTraversingState(viewStack, TraversingState.IDLE, new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + waitForTraversingState(viewStack, TraversingState.IDLE, new Runnable() { + @Override public void run() { viewStack.popBackToWithResult(layout, result); } }); @@ -123,19 +131,17 @@ public void onTraversing(@NonNull TraversingState traversingState) { * @param desiredState the traversing state to wait on * @param callback the callback to invoke */ - @MainThread - private static void waitForTraversingState(@NonNull final ViewStack viewStack, - @NonNull final TraversingState desiredState, @NonNull final ViewStackListener callback) { + @MainThread private static void waitForTraversingState(@NonNull final ViewStack viewStack, + @TraversingState final int desiredState, @NonNull final Runnable callback) { ViewUtils.verifyMainThread(); if (desiredState == viewStack.getTraversingState()) { - callback.onTraversing(desiredState); + callback.run(); } else { viewStack.addTraversingListener(new ViewStackListener() { - @Override - public void onTraversing(@NonNull TraversingState traversingState) { + @Override public void onTraversing(@TraversingState int traversingState) { if (traversingState == desiredState) { viewStack.removeTraversingListener(this); - callback.onTraversing(desiredState); + callback.run(); } } }); diff --git a/defrag/src/main/java/com/solera/defrag/ViewUtils.java b/defrag/src/main/java/com/solera/defrag/ViewUtils.java index b1ba0a6..a62a91c 100755 --- a/defrag/src/main/java/com/solera/defrag/ViewUtils.java +++ b/defrag/src/main/java/com/solera/defrag/ViewUtils.java @@ -28,53 +28,53 @@ * A collection of utilities for views. */ public class ViewUtils { - private ViewUtils() { - } + private ViewUtils() { + } - /** - * Verify that the calling thread is the Android main thread. - * - * @throws IllegalStateException when called from any other thread. - */ - public static void verifyMainThread() { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException( - "Expected to be called on the main thread but was " + Thread.currentThread().getName()); - } - } + /** + * Verify that the calling thread is the Android main thread. + * + * @throws IllegalStateException when called from any other thread. + */ + public static void verifyMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new IllegalStateException( + "Expected to be called on the main thread but was " + Thread.currentThread().getName()); + } + } - /** - * Call the given callback when the view has been measured (if it has already been measured, the - * callback is immediately invoked. - * - * @param view the view to measure - * @param callback the callback to invoke - */ - @MainThread public static void waitForMeasure(@NonNull final View view, - @NonNull final OnMeasuredCallback callback) { - int width = view.getWidth(); - int height = view.getHeight(); + /** + * Call the given callback when the view has been measured (if it has already been measured, the + * callback is immediately invoked. + * + * @param view the view to measure + * @param callback the callback to invoke + */ + @MainThread public static void waitForMeasure(@NonNull final View view, + @NonNull final OnMeasuredCallback callback) { + int width = view.getWidth(); + int height = view.getHeight(); - if (width > 0 && height > 0) { - callback.onMeasured(view, width, height); - return; - } + if (width > 0 && height > 0) { + callback.onMeasured(view, width, height); + return; + } - view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override public boolean onPreDraw() { - final ViewTreeObserver observer = view.getViewTreeObserver(); - if (observer.isAlive()) { - observer.removeOnPreDrawListener(this); - } + view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override public boolean onPreDraw() { + final ViewTreeObserver observer = view.getViewTreeObserver(); + if (observer.isAlive()) { + observer.removeOnPreDrawListener(this); + } - callback.onMeasured(view, view.getWidth(), view.getHeight()); + callback.onMeasured(view, view.getWidth(), view.getHeight()); - return true; - } - }); - } + return true; + } + }); + } - public interface OnMeasuredCallback { - void onMeasured(View view, int width, int height); - } + public interface OnMeasuredCallback { + void onMeasured(View view, int width, int height); + } } diff --git a/gradle.properties b/gradle.properties index 3acbdc6..f842e33 100755 --- a/gradle.properties +++ b/gradle.properties @@ -20,8 +20,8 @@ # Google libs android_support_lib_version = 23.4.0 -#Auto parcel lib -auto_parcel_lib_version = 0.3.1 +#Auto value +auto_value_lib_version = 1.2 #Reactive Android rxjava_lib_version = 1.1.9