From 3221552fe0b667d5f49b382121e792990357e0d8 Mon Sep 17 00:00:00 2001 From: Eduard Shchigolev Date: Tue, 15 Mar 2016 23:26:57 +1000 Subject: [PATCH] Added validation feature (#7) --- .../materialintro/demo/LoginFragment.java | 8 +- .../demo/MaterialIntroActivity.java | 49 +++++++- app/src/main/res/values/strings.xml | 2 + .../materialintro/app/IntroActivity.java | 110 ++++++++++++++++-- .../app/NavigationInterface.java | 29 +++++ .../app/SetNavigationStateInterface.java | 8 ++ .../materialintro/app/SlideFragment.java | 34 ++++++ .../materialintro/slide/FragmentSlide.java | 15 +++ .../materialintro/slide/SimpleSlide.java | 15 +++ .../materialintro/slide/Slide.java | 29 +++++ .../materialintro/util/AnimUtils.java | 9 ++ .../materialintro/view/FadeableViewPager.java | 26 +++++ library/src/main/res/anim/cycle_2.xml | 3 + library/src/main/res/anim/shake.xml | 6 + 14 files changed, 326 insertions(+), 17 deletions(-) create mode 100644 library/src/main/java/com/heinrichreimersoftware/materialintro/app/NavigationInterface.java create mode 100644 library/src/main/java/com/heinrichreimersoftware/materialintro/app/SetNavigationStateInterface.java create mode 100644 library/src/main/java/com/heinrichreimersoftware/materialintro/app/SlideFragment.java create mode 100644 library/src/main/res/anim/cycle_2.xml create mode 100644 library/src/main/res/anim/shake.xml diff --git a/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/LoginFragment.java b/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/LoginFragment.java index 87174bf..17fc581 100644 --- a/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/LoginFragment.java +++ b/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/LoginFragment.java @@ -11,7 +11,10 @@ import android.widget.EditText; import android.widget.Toast; -public class LoginFragment extends Fragment { +import com.heinrichreimersoftware.materialintro.app.IntroActivity; +import com.heinrichreimersoftware.materialintro.app.SlideFragment; + +public class LoginFragment extends SlideFragment { private EditText fakeUsername; private EditText fakePassword; @@ -26,6 +29,9 @@ public void run() { fakeLogin.setEnabled(true); fakeLogin.setText(R.string.label_fake_login); Toast.makeText(getContext(), R.string.label_fake_login_success, Toast.LENGTH_SHORT).show(); + + allowNextSlide(); + //showNextSlide(); } }; diff --git a/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/MaterialIntroActivity.java b/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/MaterialIntroActivity.java index 85ea9fa..6d13de8 100644 --- a/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/MaterialIntroActivity.java +++ b/app/src/main/java/com/heinrichreimersoftware/materialintro/demo/MaterialIntroActivity.java @@ -2,10 +2,18 @@ import android.content.Intent; import android.os.Bundle; +import android.support.v4.view.ViewPager; +import android.widget.Toast; import com.heinrichreimersoftware.materialintro.app.IntroActivity; +import com.heinrichreimersoftware.materialintro.app.NavigationInterface; +import com.heinrichreimersoftware.materialintro.app.SetNavigationStateInterface; import com.heinrichreimersoftware.materialintro.slide.FragmentSlide; import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; +import com.heinrichreimersoftware.materialintro.slide.Slide; + +import java.util.ArrayList; +import java.util.List; public class MaterialIntroActivity extends IntroActivity { @@ -16,6 +24,8 @@ public class MaterialIntroActivity extends IntroActivity { public static final String EXTRA_SKIP_ENABLED = "com.heinrichreimersoftware.materialintro.demo.EXTRA_SKIP_ENABLED"; public static final String EXTRA_FINISH_ENABLED = "com.heinrichreimersoftware.materialintro.demo.EXTRA_FINISH_ENABLED"; + private List slides; + @Override protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); @@ -33,7 +43,9 @@ protected void onCreate(Bundle savedInstanceState) { setSkipEnabled(skipEnabled); setFinishEnabled(finishEnabled); - addSlide(new SimpleSlide.Builder() + slides = new ArrayList<>(); + + slides.add(new SimpleSlide.Builder() .title(R.string.title_material_metaphor) .description(R.string.description_material_metaphor) .image(R.drawable.art_material_metaphor) @@ -41,7 +53,8 @@ protected void onCreate(Bundle savedInstanceState) { .backgroundDark(R.color.color_dark_material_metaphor) .scrollable(scrollable) .build()); - addSlide(new SimpleSlide.Builder() + + slides.add(new SimpleSlide.Builder() .title(R.string.title_material_bold) .description(R.string.description_material_bold) .image(R.drawable.art_material_bold) @@ -49,7 +62,8 @@ protected void onCreate(Bundle savedInstanceState) { .backgroundDark(R.color.color_dark_material_bold) .scrollable(scrollable) .build()); - addSlide(new SimpleSlide.Builder() + + slides.add(new SimpleSlide.Builder() .title(R.string.title_material_motion) .description(R.string.description_material_motion) .image(R.drawable.art_material_motion) @@ -57,7 +71,8 @@ protected void onCreate(Bundle savedInstanceState) { .backgroundDark(R.color.color_dark_material_motion) .scrollable(scrollable) .build()); - addSlide(new SimpleSlide.Builder() + + slides.add(new SimpleSlide.Builder() .title(R.string.title_material_shadow) .description(R.string.description_material_shadow) .image(R.drawable.art_material_shadow) @@ -67,18 +82,39 @@ protected void onCreate(Bundle savedInstanceState) { .build()); if(customFragments){ - addSlide(new FragmentSlide.Builder() + slides.add(new FragmentSlide.Builder() .background(R.color.color_custom_fragment_1) .backgroundDark(R.color.color_dark_custom_fragment_1) .fragment(LoginFragment.newInstance()) + .allowNext(false) .build()); - addSlide(new FragmentSlide.Builder() + + slides.add(new FragmentSlide.Builder() .background(R.color.color_custom_fragment_2) .backgroundDark(R.color.color_dark_custom_fragment_2) .fragment(R.layout.fragment_custom, R.style.AppThemeDark) .build()); } + addSlides(slides); + + setNavigationInterface(new NavigationInterface() { + @Override + public boolean onNextClick(int position) { + return slides.get(position).isAllowSlideNext(); + } + + @Override + public boolean onPreviousClick(int position) { + return slides.get(position).isAllowSlidePrevious(); + } + + @Override + public void onImpossibleToNavigate(int position, int direction) { + Toast.makeText(getApplicationContext(), R.string.toast_impossible_to_navigate, Toast.LENGTH_LONG).show(); + } + }); + //Feel free to add and remove page change listeners to request permissions or such /* addOnPageChangeListener(new ViewPager.OnPageChangeListener(){ @@ -99,4 +135,5 @@ public void onPageScrollStateChanged(int state) { }); */ } + } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f287537..c656d1e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,4 +30,6 @@ Successfully faked login. Take off! + + Impossible to navigate diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/app/IntroActivity.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/IntroActivity.java index e3e340d..48dc7d6 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/app/IntroActivity.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/IntroActivity.java @@ -27,19 +27,26 @@ import com.heinrichreimersoftware.materialintro.R; import com.heinrichreimersoftware.materialintro.slide.Slide; import com.heinrichreimersoftware.materialintro.slide.SlideAdapter; +import com.heinrichreimersoftware.materialintro.util.AnimUtils; import com.heinrichreimersoftware.materialintro.util.CheatSheet; import com.heinrichreimersoftware.materialintro.view.FadeableViewPager; import com.heinrichreimersoftware.materialintro.view.InkPageIndicator; import java.util.Collection; +import java.util.Iterator; import java.util.List; @SuppressLint("Registered") -public class IntroActivity extends AppCompatActivity { +public class IntroActivity extends AppCompatActivity implements SetNavigationStateInterface { + + public static final int DIRECTION_NEXT = 1; + public static final int DIRECTION_PREVIOUS = -1; + + private NavigationInterface navigationInterface; private final ArgbEvaluator evaluator = new ArgbEvaluator(); private FrameLayout frame; - private ViewPager pager; + private FadeableViewPager pager; private InkPageIndicator pagerIndicator; private View buttonNext; private View buttonSkip; @@ -51,6 +58,8 @@ public class IntroActivity extends AppCompatActivity { private int position = 0; private float positionOffset = 0; + private int positionTmp = 0; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -109,7 +118,7 @@ private void setFullscreenFlags(boolean fullscreen){ private void findViews(){ frame = (FrameLayout) findViewById(R.id.mi_frame); - pager = (ViewPager) findViewById(R.id.mi_pager); + pager = (FadeableViewPager) findViewById(R.id.mi_pager); pagerIndicator = (InkPageIndicator) findViewById(R.id.mi_pager_indicator); buttonNext = findViewById(R.id.mi_button_next); buttonSkip = findViewById(R.id.mi_button_skip); @@ -123,7 +132,7 @@ private void findViews(){ buttonNext.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - nextSlide(); + nextSlide(false); } }); buttonSkip.setOnClickListener(new View.OnClickListener() { @@ -136,14 +145,38 @@ public void onClick(View v) { CheatSheet.setup(buttonSkip); } - private void nextSlide(){ + public void nextSlide(boolean force){ int currentItem = pager.getCurrentItem(); - pager.setCurrentItem(++currentItem, true); + + if (navigationInterface == null || force) { + pager.setCurrentItem(++currentItem, true); + } + else { + if (navigationInterface.onNextClick(currentItem)) { + pager.setCurrentItem(++currentItem, true); + } + else { + AnimUtils.applyShakeAnimation(getApplicationContext(), buttonNext); + navigationInterface.onImpossibleToNavigate(currentItem, DIRECTION_NEXT); + } + } } - private void previousSlide() { + public void previousSlide(boolean force) { int currentItem = pager.getCurrentItem(); - pager.setCurrentItem(--currentItem, true); + + if (navigationInterface == null || force) { + pager.setCurrentItem(--currentItem, true); + } + else { + if (navigationInterface.onPreviousClick(currentItem)) { + pager.setCurrentItem(--currentItem, true); + } + else { + AnimUtils.applyShakeAnimation(getApplicationContext(), buttonSkip); + navigationInterface.onImpossibleToNavigate(currentItem, DIRECTION_PREVIOUS); + } + } } private void skipIfEnabled() { @@ -151,7 +184,7 @@ private void skipIfEnabled() { int count = getCount(); pager.setCurrentItem(count); } else { - previousSlide(); + previousSlide(false); } } @@ -415,18 +448,30 @@ public void removeOnPageChangeListener(ViewPager.OnPageChangeListener listener) protected void addSlide(int location, Slide object) { + object.setPosition(++positionTmp); adapter.addSlide(location, object); } protected boolean addSlide(Slide object) { + object.setPosition(++positionTmp); return adapter.addSlide(object); } protected boolean addSlides(int location, @NonNull Collection collection) { + Iterator iterator = collection.iterator(); + while(iterator.hasNext()) { + iterator.next().setPosition(++positionTmp); + } + return adapter.addSlides(location, collection); } protected boolean addSlides(@NonNull Collection collection) { + Iterator iterator = collection.iterator(); + while(iterator.hasNext()) { + iterator.next().setPosition(++positionTmp); + } + return adapter.addSlides(collection); } @@ -521,8 +566,53 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse @Override public void onPageSelected(int position) { IntroActivity.this.position = position; - updateTaskDescription(); + lockOrUnlockSwipeableIfNeeded(position); + } + } + + public void setNavigationInterface(NavigationInterface navigationInterface) { + this.navigationInterface = navigationInterface; + } + + public void setAllowNextForSlideByPosition(int position) { + getSlide(position).setAllowSlideNext(true); + } + + public void setAllowNextForSlideByFragmentTag(String tag) { + pager.setSwipeable(true); + + for (int i = 0; i < this.adapter.getCount(); i++) { + if (getSlide(i).getFragment() != null && getSlide(i).getFragment().getTag().equals(tag)) { + setAllowNextForSlideByPosition(i); + break; + } } } + + public void setAllowPreviousForSlideByPosition(int position) { + getSlide(position).setAllowSlidePrevious(true); + } + + public void setAllowPreviousForSlideByFragmentTag(String tag) { + pager.setSwipeable(true); + + for (int i = 0; i < this.adapter.getCount(); i++) { + if (getSlide(i).getFragment() != null && getSlide(i).getFragment().getTag().equals(tag)) { + setAllowPreviousForSlideByPosition(i); + break; + } + } + } + + private void lockOrUnlockSwipeableIfNeeded(int position) { + if (position < getCount()) { + if (!getSlide(position).isAllowSlideNext() || !getSlide(position).isAllowSlidePrevious()) { + pager.setSwipeable(false); + } else { + pager.setSwipeable(true); + } + } + } + } diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/app/NavigationInterface.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/NavigationInterface.java new file mode 100644 index 0000000..fa56394 --- /dev/null +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/NavigationInterface.java @@ -0,0 +1,29 @@ +package com.heinrichreimersoftware.materialintro.app; + +/** + * Created by juced on 15.03.2016. + */ +public interface NavigationInterface { + + /** + * Calling before click next. If return true - show next page + * @param position + * @return + */ + boolean onNextClick(int position); + + /** + * Calling before click previous. If return true - show previous page + * @param position + * @return + */ + boolean onPreviousClick(int position); + + /** + * Calling when impossible to navigate next or previous page + * @param position + * @param direction + */ + void onImpossibleToNavigate(int position, int direction); + +} diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SetNavigationStateInterface.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SetNavigationStateInterface.java new file mode 100644 index 0000000..4b13eff --- /dev/null +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SetNavigationStateInterface.java @@ -0,0 +1,8 @@ +package com.heinrichreimersoftware.materialintro.app; + +/** + * Created by juced on 15.03.2016. + */ +public interface SetNavigationStateInterface { + void setAllowNextForSlideByPosition(int position); +} diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SlideFragment.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SlideFragment.java new file mode 100644 index 0000000..a480c7d --- /dev/null +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/app/SlideFragment.java @@ -0,0 +1,34 @@ +package com.heinrichreimersoftware.materialintro.app; + +import android.support.v4.app.Fragment; + +/** + * Created by juced on 15.03.2016. + */ +public class SlideFragment extends Fragment { + + protected void allowNextSlide() { + if (getActivity() instanceof IntroActivity) { + ((IntroActivity) getActivity()).setAllowNextForSlideByFragmentTag(getTag()); + } + } + + protected void allowPreviousSlide() { + if (getActivity() instanceof IntroActivity) { + ((IntroActivity) getActivity()).setAllowPreviousForSlideByFragmentTag(getTag()); + } + } + + protected void showNextSlide() { + if (getActivity() instanceof IntroActivity) { + ((IntroActivity) getActivity()).nextSlide(true); + } + } + + protected void showPreviousSlide() { + if (getActivity() instanceof IntroActivity) { + ((IntroActivity) getActivity()).previousSlide(true); + } + } + +} diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/FragmentSlide.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/FragmentSlide.java index fac8910..29e93e5 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/FragmentSlide.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/FragmentSlide.java @@ -22,6 +22,8 @@ private FragmentSlide(Builder builder) { fragment = builder.fragment; background = builder.background; backgroundDark = builder.backgroundDark; + this.setAllowSlideNext(builder.allowNext); + this.setAllowSlidePrevious(builder.allowPrevious); } @Override @@ -46,6 +48,9 @@ public static class Builder{ @ColorRes private int backgroundDark = 0; + private boolean allowNext = true; + private boolean allowPrevious = true; + public Builder fragment(android.support.v4.app.Fragment fragment) { this.fragment = fragment; return this; @@ -71,6 +76,16 @@ public Builder backgroundDark(@ColorRes int backgroundDark) { return this; } + public Builder allowNext(boolean value) { + this.allowNext = value; + return this; + } + + public Builder allowPrevious(boolean value) { + this.allowPrevious = value; + return this; + } + public FragmentSlide build(){ if(background == 0 || fragment == null) throw new IllegalArgumentException("You must set at least a fragment and background."); diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/SimpleSlide.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/SimpleSlide.java index 452ae94..8bed130 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/SimpleSlide.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/SimpleSlide.java @@ -28,6 +28,8 @@ private SimpleSlide(Builder builder) { builder.background, builder.layout); background = builder.background; backgroundDark = builder.backgroundDark; + this.setAllowSlideNext(builder.allowNext); + this.setAllowSlidePrevious(builder.allowPrevious); } @Override @@ -59,6 +61,9 @@ public static class Builder{ @LayoutRes private int layout = R.layout.fragment_simple_slide; + private boolean allowNext = true; + private boolean allowPrevious = true; + public Builder background(@ColorRes int background) { this.background = background; return this; @@ -95,6 +100,16 @@ public Builder scrollable(boolean scrollable) { return this; } + public Builder allowNext(boolean value) { + this.allowNext = value; + return this; + } + + public Builder allowPrevious(boolean value) { + this.allowPrevious = value; + return this; + } + public SimpleSlide build(){ return new SimpleSlide(this); } diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/Slide.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/Slide.java index 0baf954..2deb9f9 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/Slide.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/slide/Slide.java @@ -11,4 +11,33 @@ public abstract class Slide { public int getBackgroundDark(){ return 0; } + + private int position; + + private boolean allowSlideNext = true; + private boolean allowSlidePrevious = true; + + public boolean isAllowSlideNext() { + return allowSlideNext; + } + + public boolean isAllowSlidePrevious() { + return allowSlidePrevious; + } + + public void setAllowSlidePrevious(boolean allowSlidePrevious) { + this.allowSlidePrevious = allowSlidePrevious; + } + + public void setAllowSlideNext(boolean allowSlideNext) { + this.allowSlideNext = allowSlideNext; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } } diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/util/AnimUtils.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/util/AnimUtils.java index ea49956..4c06fbb 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/util/AnimUtils.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/util/AnimUtils.java @@ -19,9 +19,13 @@ import android.content.Context; import android.os.Build; import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.view.View; +import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import com.heinrichreimersoftware.materialintro.R; + /** * Utility methods for working with animations. */ @@ -44,5 +48,10 @@ public static Interpolator getFastOutSlowInInterpolator(Context context) { return fastOutSlowIn; } + public static void applyShakeAnimation(Context context, View view) { + Animation shake; + shake = AnimationUtils.loadAnimation(context, R.anim.shake); + view.startAnimation(shake); + } } \ No newline at end of file diff --git a/library/src/main/java/com/heinrichreimersoftware/materialintro/view/FadeableViewPager.java b/library/src/main/java/com/heinrichreimersoftware/materialintro/view/FadeableViewPager.java index 723f71b..d8ce8b1 100644 --- a/library/src/main/java/com/heinrichreimersoftware/materialintro/view/FadeableViewPager.java +++ b/library/src/main/java/com/heinrichreimersoftware/materialintro/view/FadeableViewPager.java @@ -6,10 +6,14 @@ import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; public class FadeableViewPager extends ViewPager { + + private boolean swipeable = true; + public FadeableViewPager(Context context) { super(context); } @@ -253,4 +257,26 @@ public void onPageSelected(int position) { public void onPageScrollStateChanged(int state) { } } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (this.swipeable) { + return super.onInterceptTouchEvent(event); + } + + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (this.swipeable) { + return super.onTouchEvent(event); + } + + return false; + } + + public void setSwipeable(boolean swipeable) { + this.swipeable = swipeable; + } } diff --git a/library/src/main/res/anim/cycle_2.xml b/library/src/main/res/anim/cycle_2.xml new file mode 100644 index 0000000..32f644a --- /dev/null +++ b/library/src/main/res/anim/cycle_2.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/shake.xml b/library/src/main/res/anim/shake.xml new file mode 100644 index 0000000..12fc9f9 --- /dev/null +++ b/library/src/main/res/anim/shake.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file