diff --git a/.travis.yml b/.travis.yml index 1b500a5..e1e5d4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,20 +3,38 @@ language: android jdk: - oraclejdk8 +env: + global: + - ANDROID_TARGET=android-19 + - ANDROID_ABI=armeabi-v7a + android: components: - tools - tools - platform-tools - - build-tools-24.0.3 - - android-24 + - build-tools-26.0. + - android-19 + - android-26 - extra-google-google_play_services - extra-google-m2repository - extra-android-m2repository - -licenses: + - sys-img-armeabi-v7a-android-19 + licenses: - '.+' +branches: + except: + - gh-pages + +before_script: + # Create and start an emulator for instrumentation tests. + - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator + - adb shell setprop dalvik.vm.dexopt-flags v=n,o=v + - adb shell input keyevent 82 + before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ @@ -25,12 +43,20 @@ cache: directories: - $HOME/.m2 - $HOME/.gradle + - $HOME/.android/build-cache before_install: + - chmod +x gradlew - mkdir "$ANDROID_HOME/licenses" || true - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" +script: + - ./gradlew build jacocoTestReport assembleAndroidTest connectedCheck + +after_success: + - bash <(curl -s https://codecov.io/bash) + notifications: slack: secure: qIOOj57yVyinyTs9SinZmp/aVN5Or/9LDg+l9SYMqVCaqM9zDk7s1/m/L7VNPdWCuWOzLf9g1+0ReBcwZ6vh+HWBQ4T1V4HQd09whhUGyW9kMj3BKE0gWpIJLYKuhM551auv3FVzTp3u27q4W0zgiXB8qHWatTQu9rcPumG+IJaZD1uHsbhrQq0RLD8n8hWjQAdkRKRtSo4UR55sTK35uGRZbMFcyJSiStBXRP43w2kTR1MxIst4r9NeOx/sjebBQ/XxabKJgqAHhue80O3Cy8s0u59NDHOMpqJOu00cdKbtmhePQsY0FUl5/689Xdc+bDs3OcwGWbokaFEjXLwA1De+CIz0NMjgdtyHIbEGWcYav8jujke4wYaAtQRPgKHtVL9EpIUX07jPznstRNV8T3H1qrf2S5xHW6elZ7nLOnYuDKsgETmEuDQLAg8ibYQTF4zNBYGFwvC3GOJCqCu+o40OwmFghyohmXxSmo8Cg019V/hOtmYThaFcyDQhN8QGkUSqHrjDNRxyyye2JHvU+bJlTshonZlPh2gM9NA9Tf/3fMEobtnA5XYurntj43UhdZ4HdsYjbrFhbOrXEBRx1mG8gcCFgexz/3E9wq7GN0fqm6LMB8radqUbP0hAd2cADlN9suCWWLVnufLAiS5iqo55M2e9u749p+e+ESCLXIo= \ No newline at end of file diff --git a/README.md b/README.md index 05d7dc5..d36ae12 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Material intro screen is inspired by [Material Intro] and developed with love fr - [Easily add new slides][Intro Activity] - [Custom slides][Custom Slide] - [Parallax slides][Parallax Slide] - - Easy extensible api + - Easy and extensible api - Android TV support! - Material design at it's best!!! @@ -35,7 +35,7 @@ public class IntroActivity extends MaterialIntroActivity ```xml + android:theme="@style/Theme.MaterialIntro" /> ``` ### Step 4: #### [Add slides:][Intro Activity] @@ -50,15 +50,18 @@ public class IntroActivity extends MaterialIntroActivity .possiblePermissions(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.READ_SMS}) .neededPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}) .image(agency.tango.materialintroscreen.R.drawable.ic_next) + .grantPermissionMessage(R.string.txt_pls_grant_permission) + .grantPermissionError(R.string.txt_grant_permission_error) .title("title 3") .description("Description 3") .build(), - new MessageButtonBehaviour(new View.OnClickListener() { - @Override - public void onClick(View v) { - showMessage("We provide solutions to make you love your work"); - } - }, "Work with love")); + new MessageButtonBehaviour(new MessageButtonClickListener() { + @Override + public void onClick(Button messageButton) { + messageButton.setText("Click me once again!"); + showMessage("We provide solutions to make you love your work"); + } + }, "Work with love")); } ``` #### Explanation of SlideFragment usage: @@ -71,6 +74,7 @@ public class IntroActivity extends MaterialIntroActivity - ```setSkipButtonVisible()``` ⇾ show skip button instead of back button on the left bottom of screen - ```hideBackButton()``` ⇾ hides any button on the left bottom of screen - ```enableLastSlideAlphaExitTransition()``` ⇾ set if the last slide should disapear with alpha hiding effect + - ```onLastSlidePassed``` ⇾ Override in order to perform some action after passing last slide #### Customizing view animations: @@ -92,25 +96,29 @@ getBackButtonTranslationWrapper() - ```getSkipButtonTranslationWrapper()``` ## Custom slides -#### Of course you are able to implement completely custom slides. You only need to extend SlideFragment and override following functions: +#### Of course you are able to implement completely custom slides. You only need to extend SlideFragmentBase and override all needed by you functions. - ```backgroundColor()``` - ```buttonsColor()``` - ```canMoveFurther()``` (only if you want to stop user from being able to move further before he will do some action) - ```cantMoveFurtherErrorMessage()``` (as above) - + - ```neededPermissions()``` + - ```possiblePermissions()``` + - ```grantPermissionStringRes()``` + - ```grantPermissionErrorStringRes()``` + #### If you want to use parallax in a fragment please use one of the below views: - [```ParallaxFrameLayout```][ParallaxFrame] - [```ParallaxLinearLayout```][ParallaxLinear] - [```ParallaxRelativeLayout```][ParallaxRelative] -#### And set there the [app:layout_parallaxFactor][ParallaxFactor] attribute: +#### And set there the [app:mis_layout_parallaxFactor][ParallaxFactor] attribute: ```xml + app:mis_layout_parallaxFactor="0.6"/> ``` All features which are not available in simple Slide Fragment are shown here: [Custom Slide] diff --git a/app/build.gradle b/app/build.gradle index be9973a..c1b3d16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.application' +//apply from: '../jacoco-android.gradle' apply from: "$rootDir/versions.gradle" android { - compileSdkVersion 25 - buildToolsVersion "25.0.0" + compileSdkVersion project.compileSdkVersion + buildToolsVersion project.buildToolsVersion defaultConfig { applicationId "agency.tango.materialintro" - minSdkVersion 15 - targetSdkVersion 25 + minSdkVersion project.minSdkVersion + targetSdkVersion project.targetSdkVersion versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 40ed932..8b8637d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,16 +1,16 @@ + package="agency.tango.materialintro"> - - - - - + + + + + @@ -18,16 +18,16 @@ android:name=".SplashActivity" android:theme="@style/SplashScreen"> - - + + - + + android:theme="@style/Theme.MaterialIntro"/> diff --git a/app/src/main/java/agency/tango/materialintro/CustomSlide.java b/app/src/main/java/agency/tango/materialintro/CustomSlide.java index 4214cf9..ad25be2 100644 --- a/app/src/main/java/agency/tango/materialintro/CustomSlide.java +++ b/app/src/main/java/agency/tango/materialintro/CustomSlide.java @@ -7,29 +7,21 @@ import android.view.ViewGroup; import android.widget.CheckBox; -import agency.tango.materialintroscreen.SlideFragment; +import agency.tango.materialintroscreen.fragments.SlideFragmentBase; + +public class CustomSlide extends SlideFragmentBase { -public class CustomSlide extends SlideFragment { private CheckBox checkBox; @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_custom_slide, container, false); checkBox = (CheckBox) view.findViewById(R.id.checkBox); return view; } - @Override - public int backgroundColor() { - return R.color.custom_slide_background; - } - - @Override - public int buttonsColor() { - return R.color.custom_slide_buttons; - } - @Override public boolean canMoveFurther() { return checkBox.isChecked(); diff --git a/app/src/main/java/agency/tango/materialintro/IntroActivity.java b/app/src/main/java/agency/tango/materialintro/IntroActivity.java index 461a4cf..047d65a 100644 --- a/app/src/main/java/agency/tango/materialintro/IntroActivity.java +++ b/app/src/main/java/agency/tango/materialintro/IntroActivity.java @@ -5,23 +5,28 @@ import android.support.annotation.FloatRange; import android.support.annotation.Nullable; import android.view.View; +import android.widget.Button; import android.widget.Toast; import agency.tango.materialintroscreen.MaterialIntroActivity; -import agency.tango.materialintroscreen.MessageButtonBehaviour; -import agency.tango.materialintroscreen.SlideFragmentBuilder; +import agency.tango.materialintroscreen.behaviours.MessageButtonBehaviour; import agency.tango.materialintroscreen.animations.IViewTranslation; +import agency.tango.materialintroscreen.fragments.SlideFragmentBuilder; +import agency.tango.materialintroscreen.listeners.click.MessageButtonClickListener; public class IntroActivity extends MaterialIntroActivity { + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); enableLastSlideAlphaExitTransition(true); + setSkipButtonVisible(); getBackButtonTranslationWrapper() .setEnterTranslation(new IViewTranslation() { @Override - public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { + public void translate(View view, + @FloatRange(from = 0, to = 1.0) float percentage) { view.setAlpha(percentage); } }); @@ -33,13 +38,23 @@ public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentag .title("Organize your time with us") .description("Would you try?") .build(), - new MessageButtonBehaviour(new View.OnClickListener() { + new MessageButtonBehaviour(new MessageButtonClickListener() { @Override - public void onClick(View v) { + public void onClick(Button messageButton) { + messageButton.setText("Click me once again!"); showMessage("We provide solutions to make you love your work"); } }, "Work with love")); + addSlide(new SlideFragmentBuilder() + .backgroundColor(R.color.first_slide_background) + .buttonsColor(R.color.first_slide_buttons) + .image(R.drawable.img_office) + .title("Organize your time with us") + .description("Would you try?") + .neededPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}) + .build()); + addSlide(new SlideFragmentBuilder() .backgroundColor(R.color.second_slide_background) .buttonsColor(R.color.second_slide_buttons) @@ -52,15 +67,20 @@ public void onClick(View v) { addSlide(new SlideFragmentBuilder() .backgroundColor(R.color.third_slide_background) .buttonsColor(R.color.third_slide_buttons) - .possiblePermissions(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.READ_SMS}) - .neededPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}) + .possiblePermissions( + new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.READ_SMS}) + .neededPermissions(new String[]{Manifest.permission.CAMERA, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) .image(R.drawable.img_equipment) + .grantPermissionMessage(R.string.error_message) + .grantPermissionError(R.string.error_message) .title("We provide best tools") .description("ever") .build(), - new MessageButtonBehaviour(new View.OnClickListener() { + new MessageButtonBehaviour(new MessageButtonClickListener() { @Override - public void onClick(View v) { + public void onClick(Button messageButton) { showMessage("Try us!"); } }, "Tools")); @@ -71,11 +91,13 @@ public void onClick(View v) { .title("That's it") .description("Would you join us?") .build()); + + + } @Override - public void onFinish() { - super.onFinish(); + public void onLastSlidePassed() { Toast.makeText(this, "Try this library in your project! :)", Toast.LENGTH_SHORT).show(); } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c134d92..8fc4954 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,7 +11,7 @@ android:layout_gravity="center_horizontal|bottom" android:layout_margin="16dp" android:text="@string/launch_intro_activity" - android:theme="@style/ColoredButton" /> + android:theme="@style/MaterialButton" /> + app:mis_layout_parallaxFactor="0.4" /> diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index c8e30c1..4a7d424 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -1,7 +1,7 @@ - - diff --git a/build.gradle b/build.gradle index 6ab2dc7..9ee47e7 100644 --- a/build.gradle +++ b/build.gradle @@ -3,10 +3,13 @@ buildscript { repositories { jcenter() + maven { url 'https://maven.google.com' } } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' - classpath 'com.novoda:bintray-release:0.3.4' + classpath 'com.android.tools.build:gradle:3.0.1' +// classpath 'com.novoda:bintray-release:0.3.4' +// classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' +// classpath 'com.palantir:jacoco-coverage:0.4.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -15,6 +18,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url 'https://maven.google.com' } } tasks.withType(Javadoc) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04e285f..556bfea 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Mon Apr 03 12:02:18 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/jacoco-android.gradle b/jacoco-android.gradle new file mode 100644 index 0000000..883d587 --- /dev/null +++ b/jacoco-android.gradle @@ -0,0 +1,15 @@ +apply plugin: 'jacoco-android' + +jacoco { + toolVersion = "0.7.7.201606060606" +} + +android { + testOptions { + unitTests.all { + jacoco { + includeNoLocationClasses = true + } + } + } +} diff --git a/material-intro-screen/build.gradle b/material-intro-screen/build.gradle index 160a4ba..14220b0 100644 --- a/material-intro-screen/build.gradle +++ b/material-intro-screen/build.gradle @@ -1,14 +1,16 @@ apply plugin: 'com.android.library' -apply plugin: 'com.novoda.bintray-release' +//apply plugin: 'com.novoda.bintray-release' +//apply from: '../jacoco-android.gradle' apply from: "$rootDir/versions.gradle" android { - compileSdkVersion 25 - buildToolsVersion "25.0.0" + compileSdkVersion project.compileSdkVersion + buildToolsVersion project.buildToolsVersion + resourcePrefix 'mis_' defaultConfig { - minSdkVersion 15 - targetSdkVersion 25 + minSdkVersion project.minSdkVersion + targetSdkVersion project.targetSdkVersion versionCode 1 versionName "1.0" @@ -53,14 +55,15 @@ android { }) compile "com.android.support:appcompat-v7:${project.androidSupport}" compile "com.android.support:design:${project.androidSupport}" + compile "com.android.support:percent:${project.androidSupport}" } - publish { - userOrg = 'tangoagency' - groupId = 'agency.tango.android' - artifactId = 'material-intro-screen' - publishVersion = '0.0.5' - desc = '' - website = 'https://github.com/TangoAgency/material-intro-screen' - } +// publish { +// userOrg = 'tangoagency' +// groupId = 'agency.tango.android' +// artifactId = 'material-intro-screen' +// publishVersion = '0.0.5' +// desc = '' +// website = 'https://github.com/TangoAgency/material-intro-screen' +// } } \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/ISlideErrorHandler.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/ISlideErrorHandler.java new file mode 100644 index 0000000..43e45cc --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/ISlideErrorHandler.java @@ -0,0 +1,5 @@ +package agency.tango.materialintroscreen; + +public interface ISlideErrorHandler { + void handleError(); +} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MaterialIntroActivity.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MaterialIntroActivity.java index 9a96040..dd3fb3c 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MaterialIntroActivity.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MaterialIntroActivity.java @@ -4,6 +4,8 @@ import android.content.res.ColorStateList; import android.os.Build; import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.annotation.ColorInt; import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -28,20 +30,22 @@ import agency.tango.materialintroscreen.animations.wrappers.PageIndicatorTranslationWrapper; import agency.tango.materialintroscreen.animations.wrappers.SkipButtonTranslationWrapper; import agency.tango.materialintroscreen.animations.wrappers.ViewPagerTranslationWrapper; +import agency.tango.materialintroscreen.behaviours.MessageButtonBehaviour; +import agency.tango.materialintroscreen.fragments.SlideFragmentBase; import agency.tango.materialintroscreen.listeners.IFinishListener; import agency.tango.materialintroscreen.listeners.IPageScrolledListener; import agency.tango.materialintroscreen.listeners.IPageSelectedListener; import agency.tango.materialintroscreen.listeners.MessageButtonBehaviourOnPageSelected; import agency.tango.materialintroscreen.listeners.ViewBehavioursOnPageChangeListener; -import agency.tango.materialintroscreen.listeners.clickListeners.PermissionNotGrantedClickListener; -import agency.tango.materialintroscreen.listeners.scrollListeners.ParallaxScrollListener; +import agency.tango.materialintroscreen.listeners.click.PermissionNotGrantedClickListener; +import agency.tango.materialintroscreen.listeners.scroll.ParallaxScrollListener; import agency.tango.materialintroscreen.widgets.InkPageIndicator; import agency.tango.materialintroscreen.widgets.OverScrollViewPager; import agency.tango.materialintroscreen.widgets.SwipeableViewPager; -import static android.view.View.GONE; - +@SuppressWarnings("unused") public abstract class MaterialIntroActivity extends AppCompatActivity { + private SwipeableViewPager viewPager; private InkPageIndicator pageIndicator; private SlidesAdapter adapter; @@ -69,15 +73,17 @@ public abstract class MaterialIntroActivity extends AppCompatActivity { private SparseArray messageButtonBehaviours = new SparseArray<>(); @Override + @CallSuper protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = getWindow(); - window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } - setContentView(R.layout.activity_material_intro); + setContentView(R.layout.mis_activity_material_intro); overScrollLayout = (OverScrollViewPager) findViewById(R.id.view_pager_slides); viewPager = overScrollLayout.getOverScrollView(); @@ -98,7 +104,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { nextButtonTranslationWrapper = new NextButtonTranslationWrapper(nextButton); initOnPageChangeListeners(); - permissionNotGrantedClickListener = new PermissionNotGrantedClickListener(this, nextButtonTranslationWrapper); + permissionNotGrantedClickListener = new PermissionNotGrantedClickListener(this, + nextButtonTranslationWrapper); finishScreenClickListener = new FinishScreenClickListener(); setBackButtonVisible(); @@ -118,8 +125,9 @@ public void run() { } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - SlideFragment fragment = adapter.getItem(viewPager.getCurrentItem()); + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + SlideFragmentBase fragment = adapter.getItem(viewPager.getCurrentItem()); boolean hasPermissionToGrant = fragment.hasNeededPermissionsToGrant(); if (!hasPermissionToGrant) { viewPager.setSwipingRightAllowed(true); @@ -165,43 +173,42 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { } public void showPermissionsNotGrantedError() { - showError(getString(R.string.please_grant_permissions)); + showError(getString(adapter.getItem(viewPager.getCurrentItem()).grantPermissionErrorStringRes())); } /** - * Add SlideFragment to IntroScreen + * Add SlideFragmentBase to IntroScreen * - * @param slideFragment Fragment to add + * @param slideFragmentBase Fragment to add */ - @SuppressWarnings("unused") - public void addSlide(SlideFragment slideFragment) { - adapter.addItem(slideFragment); + public void addSlide(SlideFragmentBase slideFragmentBase) { + adapter.addItem(slideFragmentBase); } /** * Add SlideFragment to IntroScreen * - * @param slideFragment Fragment to add + * @param slideFragmentBase Fragment to add * @param messageButtonBehaviour Add behaviour for message button */ - @SuppressWarnings("unused") - public void addSlide(SlideFragment slideFragment, MessageButtonBehaviour messageButtonBehaviour) { - adapter.addItem(slideFragment); + public void addSlide(SlideFragmentBase slideFragmentBase, + MessageButtonBehaviour messageButtonBehaviour) { + adapter.addItem(slideFragmentBase); messageButtonBehaviours.put(adapter.getLastItemPosition(), messageButtonBehaviour); } /** * Set skip button instead of back button */ - @SuppressWarnings("unused") public void setSkipButtonVisible() { - backButton.setVisibility(GONE); + backButton.setVisibility(View.GONE); skipButton.setVisibility(View.VISIBLE); skipButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - for (int position = viewPager.getCurrentItem(); position < adapter.getCount(); position++) { + for (int position = viewPager.getCurrentItem(); position < adapter.getCount(); + position++) { if (!adapter.getItem(position).canMoveFurther()) { viewPager.setCurrentItem(position, true); showError(adapter.getItem(position).cantMoveFurtherErrorMessage()); @@ -217,7 +224,7 @@ public void onClick(View v) { * Set back button visible */ public void setBackButtonVisible() { - skipButton.setVisibility(GONE); + skipButton.setVisibility(View.GONE); backButton.setVisibility(View.VISIBLE); backButton.setOnClickListener(new View.OnClickListener() { @@ -231,7 +238,6 @@ public void onClick(View v) { /** * Hides any back button */ - @SuppressWarnings("unused") public void hideBackButton() { backButton.setVisibility(View.INVISIBLE); skipButton.setVisibility(View.GONE); @@ -251,7 +257,6 @@ public ViewTranslationWrapper getNextButtonTranslationWrapper() { * * @return ViewTranslationWrapper */ - @SuppressWarnings("unused") public ViewTranslationWrapper getBackButtonTranslationWrapper() { return backButtonTranslationWrapper; } @@ -261,7 +266,6 @@ public ViewTranslationWrapper getBackButtonTranslationWrapper() { * * @return ViewTranslationWrapper */ - @SuppressWarnings("unused") public ViewTranslationWrapper getPageIndicatorTranslationWrapper() { return pageIndicatorTranslationWrapper; } @@ -271,7 +275,6 @@ public ViewTranslationWrapper getPageIndicatorTranslationWrapper() { * * @return ViewTranslationWrapper */ - @SuppressWarnings("unused") public ViewTranslationWrapper getViewPagerTranslationWrapper() { return viewPagerTranslationWrapper; } @@ -281,7 +284,6 @@ public ViewTranslationWrapper getViewPagerTranslationWrapper() { * * @return ViewTranslationWrapper */ - @SuppressWarnings("unused") public ViewTranslationWrapper getSkipButtonTranslationWrapper() { return skipButtonTranslationWrapper; } @@ -291,7 +293,6 @@ public ViewTranslationWrapper getSkipButtonTranslationWrapper() { * * @param enableAlphaExitTransition should enable alpha exit transition */ - @SuppressWarnings("unused") public void enableLastSlideAlphaExitTransition(boolean enableAlphaExitTransition) { viewPager.alphaExitTransitionEnabled(enableAlphaExitTransition); } @@ -306,13 +307,16 @@ public void showMessage(String message) { } /** - * Override to execute this method on finish intro activity + * Override in order to perform some action after passing last slide */ - public void onFinish() { + public void onLastSlidePassed() { + // This method is intentionally empty, because we didn't want to make this method + // abstract as it would force user to implement this, even if he wouldn't like to. } private void initOnPageChangeListeners() { - messageButtonBehaviourOnPageSelected = new MessageButtonBehaviourOnPageSelected(messageButton, adapter, messageButtonBehaviours); + messageButtonBehaviourOnPageSelected = new MessageButtonBehaviourOnPageSelected( + messageButton, adapter, messageButtonBehaviours); backButtonTranslationWrapper = new BackButtonTranslationWrapper(backButton); pageIndicatorTranslationWrapper = new PageIndicatorTranslationWrapper(pageIndicator); @@ -321,11 +325,18 @@ private void initOnPageChangeListeners() { overScrollLayout.registerFinishListener(new IFinishListener() { @Override - public void doOnFinish() { + public void onFinish() { performFinish(); } }); + viewPager.registerSlideErrorHandler(new ISlideErrorHandler() { + @Override + public void handleError() { + errorOccurred(adapter.getItem(viewPager.getCurrentItem())); + } + }); + viewPager.addOnPageChangeListener(new ViewBehavioursOnPageChangeListener(adapter) .registerViewTranslationWrapper(nextButtonTranslationWrapper) .registerViewTranslationWrapper(backButtonTranslationWrapper) @@ -339,7 +350,8 @@ public void pageScrolled(final int position, float offset) { viewPager.post(new Runnable() { @Override public void run() { - if (adapter.getItem(position).hasNeededPermissionsToGrant() || !adapter.getItem(position).canMoveFurther()) { + if (adapter.getItem(position).hasNeededPermissionsToGrant() + || !adapter.getItem(position).canMoveFurther()) { viewPager.setCurrentItem(position, true); pageIndicator.clearJoiningFractions(); } @@ -364,23 +376,23 @@ public void pageSelected(int position) { } @SuppressWarnings("PointlessBooleanExpression") - private void nextButtonBehaviour(final int position, final SlideFragment fragment) { + private void nextButtonBehaviour(final int position, final SlideFragmentBase fragment) { boolean hasPermissionToGrant = fragment.hasNeededPermissionsToGrant(); if (hasPermissionToGrant) { - nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_next)); + nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.mis_ic_next)); nextButton.setOnClickListener(permissionNotGrantedClickListener); } else if (adapter.isLastSlide(position)) { - nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_finish)); + nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.mis_ic_finish)); nextButton.setOnClickListener(finishScreenClickListener); } else { - nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_next)); + nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.mis_ic_next)); nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (fragment.canMoveFurther() == false) { - errorOccurred(fragment); - } else { + if (fragment.canMoveFurther()) { viewPager.moveToNextPage(); + } else { + errorOccurred(fragment); } } }); @@ -388,7 +400,7 @@ public void onClick(View v) { } private void performFinish() { - onFinish(); + onLastSlidePassed(); finish(); } @@ -400,52 +412,66 @@ private void moveBack() { } } - private void errorOccurred(SlideFragment slideFragment) { + private void errorOccurred(SlideFragmentBase slideFragmentBase) { nextButtonTranslationWrapper.error(); - showError(slideFragment.cantMoveFurtherErrorMessage()); + showError(slideFragmentBase.cantMoveFurtherErrorMessage()); } private void showError(String error) { - Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_SHORT).setCallback(new Snackbar.Callback() { - @Override - public void onDismissed(Snackbar snackbar, int event) { - navigationView.setTranslationY(0f); - super.onDismissed(snackbar, event); - } - }).show(); + Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_SHORT) + .setCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + navigationView.setTranslationY(0f); + super.onDismissed(snackbar, event); + } + }).show(); } - private Integer getBackgroundColor(int position, float positionOffset) { - return (Integer) argbEvaluator.evaluate(positionOffset, color(adapter.getItem(position).backgroundColor()), color(adapter.getItem(position + 1).backgroundColor())); + private int getBackgroundEvaluatedColor(int position, float positionOffset) { + return (int) argbEvaluator.evaluate(positionOffset, getBackgroundColor(position), + getBackgroundColor(position + 1)); } - private Integer getButtonsColor(int position, float positionOffset) { - return (Integer) argbEvaluator.evaluate(positionOffset, color(adapter.getItem(position).buttonsColor()), color(adapter.getItem(position + 1).buttonsColor())); + private int getButtonsEvaluatedColor(int position, float positionOffset) { + return (int) argbEvaluator + .evaluate(positionOffset, getButtonsColor(position), getButtonsColor(position + 1)); } - private int color(@ColorRes int color) { + @ColorInt + private int getColorFromRes(@ColorRes int color) { return ContextCompat.getColor(this, color); } + private int getButtonsColor(int position) { + return getColorFromRes(adapter.getItem(position).buttonsColor()); + } + + private int getBackgroundColor(int position) { + return getColorFromRes(adapter.getItem(position).backgroundColor()); + } + private class ColorTransitionScrollListener implements IPageScrolledListener { + @Override public void pageScrolled(int position, float offset) { if (position < adapter.getCount() - 1) { setViewsColor(position, offset); - } else if (adapter.getCount() == 1) { - viewPager.setBackgroundColor(adapter.getItem(position).backgroundColor()); - messageButton.setTextColor(adapter.getItem(position).backgroundColor()); + } else if (adapter.getCount() == 1 || isOnLastSlide(position, offset)) { + viewPager.setBackgroundColor(getBackgroundColor(position)); + messageButton.setTextColor(getBackgroundColor(position)); + pageIndicator.setPageIndicatorColor(getButtonsColor(position)); - tintButtons(ColorStateList.valueOf(adapter.getItem(position).buttonsColor())); + tintButtons(ColorStateList.valueOf(getButtonsColor(position))); } } private void setViewsColor(int position, float offset) { - int backgroundColor = getBackgroundColor(position, offset); + int backgroundColor = getBackgroundEvaluatedColor(position, offset); viewPager.setBackgroundColor(backgroundColor); messageButton.setTextColor(backgroundColor); - int buttonsColor = getButtonsColor(position, offset); + int buttonsColor = getButtonsEvaluatedColor(position, offset); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setStatusBarColor(buttonsColor); } @@ -454,6 +480,10 @@ private void setViewsColor(int position, float offset) { tintButtons(ColorStateList.valueOf(buttonsColor)); } + private boolean isOnLastSlide(int position, float offset) { + return position == adapter.getLastItemPosition() && offset == 0; + } + private void tintButtons(ColorStateList color) { ViewCompat.setBackgroundTintList(nextButton, color); ViewCompat.setBackgroundTintList(backButton, color); @@ -462,9 +492,10 @@ private void tintButtons(ColorStateList color) { } private class FinishScreenClickListener implements View.OnClickListener { + @Override public void onClick(View v) { - SlideFragment slideFragment = adapter.getItem(adapter.getLastItemPosition()); + SlideFragmentBase slideFragment = adapter.getItem(adapter.getLastItemPosition()); if (!slideFragment.canMoveFurther()) { errorOccurred(slideFragment); } else { diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragment.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragment.java deleted file mode 100644 index ba15ac6..0000000 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragment.java +++ /dev/null @@ -1,169 +0,0 @@ -package agency.tango.materialintroscreen; - -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import agency.tango.materialintroscreen.parallax.ParallaxFragment; - -public class SlideFragment extends ParallaxFragment { - private final static String BACKGROUND_COLOR = "background_color"; - private static final String BUTTONS_COLOR = "buttons_color"; - private static final String TITLE = "title"; - private static final String DESCRIPTION = "description"; - private static final String NEEDED_PERMISSIONS = "needed_permission"; - private static final String POSSIBLE_PERMISSIONS = "possible_permission"; - private static final String IMAGE = "image"; - private static final int PERMISSIONS_REQUEST_CODE = 15621; - - private int backgroundColor; - private int buttonsColor; - private int image; - private String title; - private String description; - private String[] neededPermissions; - private String[] possiblePermissions; - - private TextView titleTextView; - private TextView descriptionTextView; - private ImageView imageView; - - public static SlideFragment createInstance(SlideFragmentBuilder builder) { - SlideFragment slideFragment = new SlideFragment(); - - Bundle bundle = new Bundle(); - bundle.putInt(BACKGROUND_COLOR, builder.backgroundColor); - bundle.putInt(BUTTONS_COLOR, builder.buttonsColor); - bundle.putInt(IMAGE, builder.image); - bundle.putString(TITLE, builder.title); - bundle.putString(DESCRIPTION, builder.description); - bundle.putStringArray(NEEDED_PERMISSIONS, builder.neededPermissions); - bundle.putStringArray(POSSIBLE_PERMISSIONS, builder.possiblePermissions); - - slideFragment.setArguments(bundle); - return slideFragment; - } - - public static boolean isNotNullOrEmpty(String string) { - return string != null && !string.isEmpty(); - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_slide, container, false); - titleTextView = (TextView) view.findViewById(R.id.txt_title_slide); - descriptionTextView = (TextView) view.findViewById(R.id.txt_description_slide); - imageView = (ImageView) view.findViewById(R.id.image_slide); - initializeView(); - return view; - } - - public void initializeView() { - Bundle bundle = getArguments(); - backgroundColor = bundle.getInt(BACKGROUND_COLOR); - buttonsColor = bundle.getInt(BUTTONS_COLOR); - image = bundle.getInt(IMAGE, 0); - title = bundle.getString(TITLE); - description = bundle.getString(DESCRIPTION); - neededPermissions = bundle.getStringArray(NEEDED_PERMISSIONS); - possiblePermissions = bundle.getStringArray(POSSIBLE_PERMISSIONS); - - updateViewWithValues(); - } - - public int backgroundColor() { - return backgroundColor; - } - - public int buttonsColor() { - return buttonsColor; - } - - public boolean hasAnyPermissionsToGrant() { - boolean hasPermissionToGrant = hasPermissionsToGrant(neededPermissions); - if (!hasPermissionToGrant) { - hasPermissionToGrant = hasPermissionsToGrant(possiblePermissions); - } - return hasPermissionToGrant; - } - - public boolean hasNeededPermissionsToGrant() { - return hasPermissionsToGrant(neededPermissions); - } - - public boolean canMoveFurther() { - return true; - } - - public String cantMoveFurtherErrorMessage() { - return getString(R.string.impassable_slide); - } - - private void updateViewWithValues() { - titleTextView.setText(title); - descriptionTextView.setText(description); - - if (image != 0) { - imageView.setImageDrawable(ContextCompat.getDrawable(getActivity(), image)); - imageView.setVisibility(View.VISIBLE); - } - } - - public void askForPermissions() { - ArrayList notGrantedPermissions = new ArrayList<>(); - - if (neededPermissions != null) { - for (String permission : neededPermissions) { - if (isNotNullOrEmpty(permission)) { - if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { - notGrantedPermissions.add(permission); - } - } - } - } - if (possiblePermissions != null) { - for (String permission : possiblePermissions) { - if (isNotNullOrEmpty(permission)) { - if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { - notGrantedPermissions.add(permission); - } - } - } - } - - String[] permissionsToGrant = removeEmptyAndNullStrings(notGrantedPermissions); - ActivityCompat.requestPermissions(getActivity(), permissionsToGrant, PERMISSIONS_REQUEST_CODE); - } - - private boolean hasPermissionsToGrant(String[] permissions) { - if (permissions != null) { - for (String permission : permissions) { - if (isNotNullOrEmpty(permission)) { - if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { - return true; - } - } - } - } - return false; - } - - @SuppressWarnings("SuspiciousMethodCalls") - private String[] removeEmptyAndNullStrings(final ArrayList permissions) { - List list = new ArrayList<>(permissions); - list.removeAll(Collections.singleton(null)); - return list.toArray(new String[list.size()]); - } -} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragmentBuilder.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragmentBuilder.java deleted file mode 100644 index f32b5ae..0000000 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragmentBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -package agency.tango.materialintroscreen; - -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; - -@SuppressWarnings({"unused", "WeakerAccess"}) -public class SlideFragmentBuilder { - int backgroundColor; - int buttonsColor; - String title; - String description; - String[] neededPermissions; - String[] possiblePermissions; - int image; - - public SlideFragmentBuilder backgroundColor(@ColorRes int backgroundColor) { - this.backgroundColor = backgroundColor; - return this; - } - - public SlideFragmentBuilder buttonsColor(@ColorRes int buttonsColor) { - this.buttonsColor = buttonsColor; - return this; - } - - public SlideFragmentBuilder title(String title) { - this.title = title; - return this; - } - - public SlideFragmentBuilder description(String description) { - this.description = description; - return this; - } - - public SlideFragmentBuilder neededPermissions(String[] neededPermissions) { - this.neededPermissions = neededPermissions; - return this; - } - - public SlideFragmentBuilder possiblePermissions(String[] possiblePermissions) { - this.possiblePermissions = possiblePermissions; - return this; - } - - public SlideFragmentBuilder image(@DrawableRes int image) { - this.image = image; - return this; - } - - public SlideFragment build() { - return SlideFragment.createInstance(this); - } -} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/adapter/SlidesAdapter.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/adapter/SlidesAdapter.java index b73fd38..ac1e7ec 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/adapter/SlidesAdapter.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/adapter/SlidesAdapter.java @@ -6,23 +6,24 @@ import java.util.ArrayList; -import agency.tango.materialintroscreen.SlideFragment; +import agency.tango.materialintroscreen.fragments.SlideFragmentBase; public class SlidesAdapter extends FragmentStatePagerAdapter { - private ArrayList fragments = new ArrayList<>(); + + private ArrayList fragments = new ArrayList<>(); public SlidesAdapter(FragmentManager fragmentManager) { super(fragmentManager); } @Override - public SlideFragment getItem(int position) { + public SlideFragmentBase getItem(int position) { return fragments.get(position); } @Override public Object instantiateItem(ViewGroup container, int position) { - SlideFragment fragment = (SlideFragment) super.instantiateItem(container, position); + SlideFragmentBase fragment = (SlideFragmentBase) super.instantiateItem(container, position); fragments.set(position, fragment); return fragment; } @@ -32,7 +33,7 @@ public int getCount() { return fragments.size(); } - public void addItem(SlideFragment fragment) { + public void addItem(SlideFragmentBase fragment) { fragments.add(getCount(), fragment); notifyDataSetChanged(); } @@ -50,7 +51,7 @@ public boolean shouldFinish(int position) { } public boolean shouldLockSlide(int position) { - SlideFragment fragment = getItem(position); + SlideFragmentBase fragment = getItem(position); return !fragment.canMoveFurther() || fragment.hasNeededPermissionsToGrant(); } } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/EnterDefaultTranslation.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/EnterDefaultTranslation.java index 48905f0..eeee4db 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/EnterDefaultTranslation.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/EnterDefaultTranslation.java @@ -9,6 +9,6 @@ public class EnterDefaultTranslation implements IViewTranslation { @Override public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { - view.setTranslationY((1f - percentage) * view.getResources().getDimensionPixelOffset(R.dimen.y_offset)); + view.setTranslationY((1f - percentage) * view.getResources().getDimensionPixelOffset(R.dimen.mis_y_offset)); } } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/ExitDefaultTranslation.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/ExitDefaultTranslation.java index a1d6693..45dcca3 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/ExitDefaultTranslation.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/ExitDefaultTranslation.java @@ -9,6 +9,6 @@ public class ExitDefaultTranslation implements IViewTranslation { @Override public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { - view.setTranslationY(percentage * view.getResources().getDimensionPixelOffset(R.dimen.y_offset)); + view.setTranslationY(percentage * view.getResources().getDimensionPixelOffset(R.dimen.mis_y_offset)); } } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/NoTranslation.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/NoTranslation.java index 2b1004d..a4b4859 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/NoTranslation.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/NoTranslation.java @@ -8,5 +8,6 @@ public class NoTranslation implements IViewTranslation { @Override public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { + //This method is intentionally left blank, as it should do nothing } } \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/NextButtonTranslationWrapper.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/NextButtonTranslationWrapper.java index 3d8baaf..8a294fb 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/NextButtonTranslationWrapper.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/NextButtonTranslationWrapper.java @@ -13,6 +13,6 @@ public NextButtonTranslationWrapper(View view) { setExitTranslation(new ExitDefaultTranslation()) .setDefaultTranslation(new DefaultPositionTranslation()) - .setErrorAnimation(R.anim.shake_it); + .setErrorAnimation(R.anim.mis_shake_it); } } \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MessageButtonBehaviour.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MessageButtonBehaviour.java similarity index 56% rename from material-intro-screen/src/main/java/agency/tango/materialintroscreen/MessageButtonBehaviour.java rename to material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MessageButtonBehaviour.java index 4db0ea9..204d680 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MessageButtonBehaviour.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MessageButtonBehaviour.java @@ -1,13 +1,14 @@ -package agency.tango.materialintroscreen; +package agency.tango.materialintroscreen.behaviours; -import android.view.View; +import agency.tango.materialintroscreen.listeners.click.MessageButtonClickListener; @SuppressWarnings("unused") public class MessageButtonBehaviour { - private View.OnClickListener clickListener; + + private MessageButtonClickListener clickListener; private String messageButtonText; - public MessageButtonBehaviour(View.OnClickListener clickListener, String messageButtonText) { + public MessageButtonBehaviour(MessageButtonClickListener clickListener, String messageButtonText) { this.clickListener = clickListener; this.messageButtonText = messageButtonText; } @@ -16,7 +17,7 @@ public MessageButtonBehaviour(String messageButtonText) { this.messageButtonText = messageButtonText; } - public View.OnClickListener getClickListener() { + public MessageButtonClickListener getClickListener() { return clickListener; } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MoveUpBehaviour.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MoveUpBehaviour.java similarity index 88% rename from material-intro-screen/src/main/java/agency/tango/materialintroscreen/MoveUpBehaviour.java rename to material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MoveUpBehaviour.java index 2be956c..77d2429 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/MoveUpBehaviour.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/behaviours/MoveUpBehaviour.java @@ -1,4 +1,4 @@ -package agency.tango.materialintroscreen; +package agency.tango.materialintroscreen.behaviours; import android.content.Context; import android.support.design.widget.CoordinatorLayout; @@ -9,6 +9,7 @@ @SuppressWarnings("unused") public class MoveUpBehaviour extends CoordinatorLayout.Behavior { + public MoveUpBehaviour(Context context, AttributeSet attrs) { super(context, attrs); } @@ -19,7 +20,8 @@ public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, Vie } @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { + public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, + View dependency) { float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight()); child.setTranslationY(translationY); return true; diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragment.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragment.java new file mode 100644 index 0000000..4f1d47b --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragment.java @@ -0,0 +1,145 @@ +package agency.tango.materialintroscreen.fragments; + +import android.os.Bundle; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.content.ContextCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import agency.tango.materialintroscreen.R; + +public class SlideFragment extends SlideFragmentBase { + + public static final String BACKGROUND_COLOR = "background_color"; + public static final String BUTTONS_COLOR = "buttons_color"; + public static final String TITLE = "title"; + public static final String DESCRIPTION = "description"; + public static final String NEEDED_PERMISSIONS = "needed_permission"; + public static final String POSSIBLE_PERMISSIONS = "possible_permission"; + public static final String IMAGE = "image"; + public static final String GRANT_PERMISSION_MESSAGE = "grant_permission_message"; + public static final String GRANT_PERMISSION_ERROR = "grant_permission_error"; + + @ColorRes + private int backgroundColor; + + @ColorRes + private int buttonsColor; + + @DrawableRes + private int image; + + @StringRes + private int grantPermissionStringRes; + + @StringRes + private int grantPermissionErrorStringRes; + + private String title; + private String description; + private String[] neededPermissions; + private String[] possiblePermissions; + + private TextView titleTextView; + private TextView descriptionTextView; + private ImageView imageView; + + public static SlideFragment createInstance(Bundle bundle) { + SlideFragment slideFragment = new SlideFragment(); + slideFragment.setArguments(bundle); + return slideFragment; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.mis_fragment_slide, container, false); + titleTextView = (TextView) view.findViewById(R.id.txt_title_slide); + descriptionTextView = (TextView) view.findViewById(R.id.txt_description_slide); + imageView = (ImageView) view.findViewById(R.id.image_slide); + initializeView(); + return view; + } + + @Override + @ColorRes + public int backgroundColor() { + return backgroundColor; + } + + @Override + @ColorRes + public int buttonsColor() { + return buttonsColor; + } + + @Override + public String[] possiblePermissions() { + return possiblePermissions; + } + + @Override + public String[] neededPermissions() { + return neededPermissions; + } + + @Override + public boolean canMoveFurther() { + return true; + } + + @Override + public String cantMoveFurtherErrorMessage() { + return getString(R.string.mis_impassable_slide); + } + + @Override + public int grantPermissionStringRes() { + return grantPermissionStringRes; + } + + @Override + public int grantPermissionErrorStringRes() { + return grantPermissionErrorStringRes; + } + + private void initializeView() { + Bundle bundle = getArguments(); + backgroundColor = bundle.getInt(BACKGROUND_COLOR); + buttonsColor = bundle.getInt(BUTTONS_COLOR); + image = bundle.getInt(IMAGE, 0); + title = bundle.getString(TITLE); + description = bundle.getString(DESCRIPTION); + neededPermissions = bundle.getStringArray(NEEDED_PERMISSIONS); + possiblePermissions = bundle.getStringArray(POSSIBLE_PERMISSIONS); + grantPermissionStringRes = bundle.getInt(GRANT_PERMISSION_MESSAGE); + grantPermissionErrorStringRes = bundle.getInt(GRANT_PERMISSION_ERROR); + + updateViewWithValues(); + } + + private void updateViewWithValues() { + titleTextView.setText(title); + descriptionTextView.setText(description); + + if (image != 0) { + imageView.setImageDrawable(ContextCompat.getDrawable(getActivity(), image)); + imageView.setVisibility(View.VISIBLE); + } + + if (grantPermissionStringRes == 0) { + grantPermissionStringRes = R.string.mis_grant_permissions; + } + + if (grantPermissionErrorStringRes == 0) { + grantPermissionErrorStringRes = R.string.mis_please_grant_permissions; + } + } +} \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBase.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBase.java new file mode 100644 index 0000000..80d7925 --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBase.java @@ -0,0 +1,133 @@ +package agency.tango.materialintroscreen.fragments; + +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.annotation.ColorRes; +import android.support.annotation.StringRes; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import agency.tango.materialintroscreen.R; +import agency.tango.materialintroscreen.parallax.ParallaxFragment; + +public class SlideFragmentBase extends ParallaxFragment { + + private static final int PERMISSIONS_REQUEST_CODE = 15621; + + @ColorRes + public int backgroundColor() { + return R.color.mis_default_background_color; + } + + @ColorRes + public int buttonsColor() { + return R.color.mis_default_buttons_color; + } + + public boolean canMoveFurther() { + return true; + } + + public String cantMoveFurtherErrorMessage() { + return getString(R.string.mis_impassable_slide); + } + + public String[] possiblePermissions() { + return new String[0]; + } + + public String[] neededPermissions() { + return new String[0]; + } + + @StringRes + public int grantPermissionStringRes() { + return R.string.mis_grant_permissions; + } + + @StringRes + public int grantPermissionErrorStringRes() { + return R.string.mis_please_grant_permissions; + } + + public boolean hasAnyPermissionsToGrant() { + if (!isAndroidVersionSupportingPermissions()) { + return false; + } + + boolean hasPermissionToGrant = hasPermissionsToGrant(neededPermissions()); + if (!hasPermissionToGrant) { + hasPermissionToGrant = hasPermissionsToGrant(possiblePermissions()); + } + return hasPermissionToGrant; + } + + public boolean hasNeededPermissionsToGrant() { + return hasPermissionsToGrant(neededPermissions()); + } + + @SuppressWarnings({"PMD.CollapsibleIfStatements"}) + public void askForPermissions() { + ArrayList notGrantedPermissions = new ArrayList<>(); + + if (neededPermissions() != null) { + for (String permission : neededPermissions()) { + if (!TextUtils.isEmpty(permission)) { + if (ContextCompat.checkSelfPermission(getContext(), permission) + != PackageManager.PERMISSION_GRANTED) { + notGrantedPermissions.add(permission); + } + } + } + } + if (possiblePermissions() != null) { + for (String permission : possiblePermissions()) { + if (!TextUtils.isEmpty(permission)) { + if (ContextCompat.checkSelfPermission(getContext(), permission) + != PackageManager.PERMISSION_GRANTED) { + notGrantedPermissions.add(permission); + } + } + } + } + + String[] permissionsToGrant = removeEmptyAndNullStrings(notGrantedPermissions); + ActivityCompat + .requestPermissions(getActivity(), permissionsToGrant, PERMISSIONS_REQUEST_CODE); + } + + @SuppressWarnings({"PMD.CollapsibleIfStatements"}) + private boolean hasPermissionsToGrant(String[] permissions) { + if (!isAndroidVersionSupportingPermissions()) { + return false; + } + + if (permissions != null) { + for (String permission : permissions) { + if (!TextUtils.isEmpty(permission)) { + if (ContextCompat.checkSelfPermission(getContext(), permission) + != PackageManager.PERMISSION_GRANTED) { + return true; + } + } + } + } + return false; + } + + @SuppressWarnings("SuspiciousMethodCalls") + private String[] removeEmptyAndNullStrings(final ArrayList permissions) { + List list = new ArrayList<>(permissions); + list.removeAll(Collections.singleton(null)); + return list.toArray(new String[list.size()]); + } + + private boolean isAndroidVersionSupportingPermissions() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } +} \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBuilder.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBuilder.java new file mode 100644 index 0000000..51d5660 --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/fragments/SlideFragmentBuilder.java @@ -0,0 +1,113 @@ +package agency.tango.materialintroscreen.fragments; + +import android.os.Bundle; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import static agency.tango.materialintroscreen.fragments.SlideFragment.BACKGROUND_COLOR; +import static agency.tango.materialintroscreen.fragments.SlideFragment.BUTTONS_COLOR; +import static agency.tango.materialintroscreen.fragments.SlideFragment.DESCRIPTION; +import static agency.tango.materialintroscreen.fragments.SlideFragment.GRANT_PERMISSION_ERROR; +import static agency.tango.materialintroscreen.fragments.SlideFragment.GRANT_PERMISSION_MESSAGE; +import static agency.tango.materialintroscreen.fragments.SlideFragment.IMAGE; +import static agency.tango.materialintroscreen.fragments.SlideFragment.NEEDED_PERMISSIONS; +import static agency.tango.materialintroscreen.fragments.SlideFragment.POSSIBLE_PERMISSIONS; +import static agency.tango.materialintroscreen.fragments.SlideFragment.TITLE; + +@SuppressWarnings("unused") +public class SlideFragmentBuilder { + + @ColorRes + private int backgroundColor; + + @ColorRes + private int buttonsColor; + + @DrawableRes + private int image; + + @StringRes + private int grantPermissionMessage; + + @StringRes + private int grantPermissionError; + + private String title; + private String description; + private String[] neededPermissions; + private String[] possiblePermissions; + + public SlideFragmentBuilder backgroundColor(@ColorRes int backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public SlideFragmentBuilder buttonsColor(@ColorRes int buttonsColor) { + this.buttonsColor = buttonsColor; + return this; + } + + public SlideFragmentBuilder title(String title) { + this.title = title; + return this; + } + + public SlideFragmentBuilder description(String description) { + this.description = description; + return this; + } + + public SlideFragmentBuilder neededPermissions(String[] neededPermissions) { + this.neededPermissions = neededPermissions; + return this; + } + + public SlideFragmentBuilder possiblePermissions(String[] possiblePermissions) { + this.possiblePermissions = possiblePermissions; + return this; + } + + public SlideFragmentBuilder image(@DrawableRes int image) { + this.image = image; + return this; + } + + public SlideFragmentBuilder grantPermissionMessage(@StringRes int grantPermissionMessage) { + this.grantPermissionMessage = grantPermissionMessage; + return this; + } + + public SlideFragmentBuilder grantPermissionError(int grantPermissionError) { + this.grantPermissionError = grantPermissionError; + return this; + } + + + public SlideFragment build() { + String missing = ""; + if (backgroundColor == 0) { + missing += " backgroundColor"; + } + if (buttonsColor == 0) { + missing += " buttonsColor"; + } + if (!missing.isEmpty()) { + throw new IllegalStateException( + "Missing required properties in SlideFragmentBuilder:" + missing); + } + + Bundle bundle = new Bundle(); + bundle.putInt(BACKGROUND_COLOR, backgroundColor); + bundle.putInt(BUTTONS_COLOR, buttonsColor); + bundle.putInt(IMAGE, image); + bundle.putString(TITLE, title); + bundle.putString(DESCRIPTION, description); + bundle.putStringArray(NEEDED_PERMISSIONS, neededPermissions); + bundle.putStringArray(POSSIBLE_PERMISSIONS, possiblePermissions); + bundle.putInt(GRANT_PERMISSION_MESSAGE, grantPermissionMessage); + bundle.putInt(GRANT_PERMISSION_ERROR, grantPermissionError); + + return SlideFragment.createInstance(bundle); + } +} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IFinishListener.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IFinishListener.java index 8a013bb..37ed789 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IFinishListener.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IFinishListener.java @@ -1,5 +1,5 @@ package agency.tango.materialintroscreen.listeners; public interface IFinishListener { - void doOnFinish(); + void onFinish(); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/MessageButtonBehaviourOnPageSelected.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/MessageButtonBehaviourOnPageSelected.java index 3ee439d..3b92155 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/MessageButtonBehaviourOnPageSelected.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/MessageButtonBehaviourOnPageSelected.java @@ -1,35 +1,38 @@ package agency.tango.materialintroscreen.listeners; +import android.text.TextUtils; import android.util.SparseArray; import android.view.View; import android.view.animation.AnimationUtils; import android.widget.Button; -import agency.tango.materialintroscreen.MessageButtonBehaviour; import agency.tango.materialintroscreen.R; -import agency.tango.materialintroscreen.SlideFragment; import agency.tango.materialintroscreen.adapter.SlidesAdapter; +import agency.tango.materialintroscreen.behaviours.MessageButtonBehaviour; +import agency.tango.materialintroscreen.fragments.SlideFragmentBase; -import static agency.tango.materialintroscreen.SlideFragment.isNotNullOrEmpty; public class MessageButtonBehaviourOnPageSelected implements IPageSelectedListener { - private Button messageButton; - private SlidesAdapter adapter; - private SparseArray messageButtonBehaviours; - public MessageButtonBehaviourOnPageSelected(Button messageButton, SlidesAdapter adapter, SparseArray messageButtonBehaviours) { + private final Button messageButton; + private final SlidesAdapter adapter; + private final SparseArray messageButtonBehaviours; + + public MessageButtonBehaviourOnPageSelected(Button messageButton, SlidesAdapter adapter, + SparseArray messageButtonBehaviours) { this.messageButton = messageButton; this.adapter = adapter; this.messageButtonBehaviours = messageButtonBehaviours; } @Override - public void pageSelected(int position) { - final SlideFragment slideFragment = adapter.getItem(position); + public void pageSelected(final int position) { + final SlideFragmentBase slideFragment = adapter.getItem(position); if (slideFragment.hasAnyPermissionsToGrant()) { showMessageButton(slideFragment); - messageButton.setText(slideFragment.getActivity().getString(R.string.grant_permissions)); + messageButton.setText( + slideFragment.getString(adapter.getItem(position).grantPermissionStringRes())); messageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -39,22 +42,31 @@ public void onClick(View view) { } else if (checkIfMessageButtonHasBehaviour(position)) { showMessageButton(slideFragment); messageButton.setText(messageButtonBehaviours.get(position).getMessageButtonText()); - messageButton.setOnClickListener(messageButtonBehaviours.get(position).getClickListener()); + messageButton + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + messageButtonBehaviours.get(position).getClickListener().onClick(messageButton); + } + }); } else if (messageButton.getVisibility() != View.INVISIBLE) { - messageButton.startAnimation(AnimationUtils.loadAnimation(slideFragment.getContext(), R.anim.fade_out)); + messageButton.startAnimation( + AnimationUtils.loadAnimation(slideFragment.getContext(), R.anim.mis_fade_out)); messageButton.setVisibility(View.INVISIBLE); } } private boolean checkIfMessageButtonHasBehaviour(int position) { - return messageButtonBehaviours.get(position) != null && isNotNullOrEmpty(messageButtonBehaviours.get(position).getMessageButtonText()); + return messageButtonBehaviours.get(position) != null && !TextUtils.isEmpty( + messageButtonBehaviours.get(position).getMessageButtonText()); } - private void showMessageButton(final SlideFragment fragment) { + private void showMessageButton(final SlideFragmentBase fragment) { if (messageButton.getVisibility() != View.VISIBLE) { messageButton.setVisibility(View.VISIBLE); if (fragment.getActivity() != null) { - messageButton.startAnimation(AnimationUtils.loadAnimation(fragment.getActivity(), R.anim.fade_in)); + messageButton.startAnimation( + AnimationUtils.loadAnimation(fragment.getActivity(), R.anim.mis_fade_in)); } } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/ViewBehavioursOnPageChangeListener.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/ViewBehavioursOnPageChangeListener.java index dc8804c..3dbd2eb 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/ViewBehavioursOnPageChangeListener.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/ViewBehavioursOnPageChangeListener.java @@ -9,6 +9,7 @@ import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; public class ViewBehavioursOnPageChangeListener implements CustomViewPager.OnPageChangeListener { + private final SlidesAdapter adapter; private List listeners = new ArrayList<>(); @@ -19,17 +20,20 @@ public ViewBehavioursOnPageChangeListener(SlidesAdapter adapter) { this.adapter = adapter; } - public ViewBehavioursOnPageChangeListener registerPageSelectedListener(IPageSelectedListener pageSelectedListener) { + public ViewBehavioursOnPageChangeListener registerPageSelectedListener( + IPageSelectedListener pageSelectedListener) { listeners.add(pageSelectedListener); return this; } - public ViewBehavioursOnPageChangeListener registerViewTranslationWrapper(ViewTranslationWrapper wrapper) { + public ViewBehavioursOnPageChangeListener registerViewTranslationWrapper( + ViewTranslationWrapper wrapper) { wrappers.add(wrapper); return this; } - public ViewBehavioursOnPageChangeListener registerOnPageScrolled(IPageScrolledListener pageScrolledListener) { + public ViewBehavioursOnPageChangeListener registerOnPageScrolled( + IPageScrolledListener pageScrolledListener) { pageScrolledListeners.add(pageScrolledListener); return this; } @@ -64,6 +68,7 @@ public void onPageSelected(int position) { @Override public void onPageScrollStateChanged(int state) { + //This method is intentionally left blank, as it should do nothing } private boolean isFirstSlide(int position) { diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/MessageButtonClickListener.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/MessageButtonClickListener.java new file mode 100644 index 0000000..31623f7 --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/MessageButtonClickListener.java @@ -0,0 +1,7 @@ +package agency.tango.materialintroscreen.listeners.click; + +import android.widget.Button; + +public interface MessageButtonClickListener { + void onClick(Button messageButton); +} \ No newline at end of file diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/clickListeners/PermissionNotGrantedClickListener.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/PermissionNotGrantedClickListener.java similarity index 91% rename from material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/clickListeners/PermissionNotGrantedClickListener.java rename to material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/PermissionNotGrantedClickListener.java index b931287..ab63067 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/clickListeners/PermissionNotGrantedClickListener.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/click/PermissionNotGrantedClickListener.java @@ -1,4 +1,4 @@ -package agency.tango.materialintroscreen.listeners.clickListeners; +package agency.tango.materialintroscreen.listeners.click; import android.view.View; diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scrollListeners/ParallaxScrollListener.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scroll/ParallaxScrollListener.java similarity index 86% rename from material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scrollListeners/ParallaxScrollListener.java rename to material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scroll/ParallaxScrollListener.java index 0493e2e..c9b7a8f 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scrollListeners/ParallaxScrollListener.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scroll/ParallaxScrollListener.java @@ -1,14 +1,15 @@ -package agency.tango.materialintroscreen.listeners.scrollListeners; +package agency.tango.materialintroscreen.listeners.scroll; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import agency.tango.materialintroscreen.SlideFragment; import agency.tango.materialintroscreen.adapter.SlidesAdapter; +import agency.tango.materialintroscreen.fragments.SlideFragmentBase; import agency.tango.materialintroscreen.listeners.IPageScrolledListener; import agency.tango.materialintroscreen.parallax.Parallaxable; public class ParallaxScrollListener implements IPageScrolledListener { + private SlidesAdapter adapter; public ParallaxScrollListener(SlidesAdapter adapter) { @@ -33,7 +34,7 @@ public void pageScrolled(int position, float offset) { } @Nullable - private SlideFragment getNextFragment(int position) { + private SlideFragmentBase getNextFragment(int position) { if (position < adapter.getLastItemPosition()) { return adapter.getItem(position + 1); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFrameLayout.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFrameLayout.java index 2fb4f6d..30fb215 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFrameLayout.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFrameLayout.java @@ -60,8 +60,8 @@ public static class LayoutParams extends FrameLayout.LayoutParams { LayoutParams(Context context, AttributeSet attributeSet) { super(context, attributeSet); - TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); - parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); + TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.mis_ParallaxLayout_Layout); + parallaxFactor = typedArray.getFloat(R.styleable.mis_ParallaxLayout_Layout_mis_layout_parallaxFactor, parallaxFactor); typedArray.recycle(); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxLinearLayout.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxLinearLayout.java index 3fa2743..41c2423 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxLinearLayout.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxLinearLayout.java @@ -60,8 +60,8 @@ public static class LayoutParams extends LinearLayout.LayoutParams { LayoutParams(Context context, AttributeSet attributeSet) { super(context, attributeSet); - TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); - parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); + TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.mis_ParallaxLayout_Layout); + parallaxFactor = typedArray.getFloat(R.styleable.mis_ParallaxLayout_Layout_mis_layout_parallaxFactor, parallaxFactor); typedArray.recycle(); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentFrameLayout.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentFrameLayout.java new file mode 100644 index 0000000..93d742b --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentFrameLayout.java @@ -0,0 +1,87 @@ +package agency.tango.materialintroscreen.parallax; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.FloatRange; +import android.support.percent.PercentFrameLayout; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import agency.tango.materialintroscreen.R; + +public class ParallaxPercentFrameLayout extends PercentFrameLayout implements Parallaxable { + + public ParallaxPercentFrameLayout(Context context) { + super(context); + } + + public ParallaxPercentFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ParallaxPercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + @Override + public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { + for (int i = getChildCount() - 1; i >= 0; i--) { + View child = getChildAt(i); + LayoutParams p = (LayoutParams) child.getLayoutParams(); + if (p.parallaxFactor == 0) + continue; + child.setTranslationX(getWidth() * -offset * p.parallaxFactor); + } + } + + public static class LayoutParams extends PercentFrameLayout.LayoutParams { + float parallaxFactor = 0f; + + LayoutParams(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.mis_ParallaxLayout_Layout); + parallaxFactor = typedArray.getFloat(R.styleable.mis_ParallaxLayout_Layout_mis_layout_parallaxFactor, parallaxFactor); + typedArray.recycle(); + } + + LayoutParams(int width, int height) { + super(width, height); + } + + @SuppressWarnings("unused") + LayoutParams(int width, int height, int gravity) { + super(width, height, gravity); + } + + LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + @SuppressWarnings("unused") + LayoutParams(MarginLayoutParams source) { + super(source); + } + } +} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentRelativeLayout.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentRelativeLayout.java new file mode 100644 index 0000000..3b6a200 --- /dev/null +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxPercentRelativeLayout.java @@ -0,0 +1,82 @@ +package agency.tango.materialintroscreen.parallax; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.FloatRange; +import android.support.percent.PercentRelativeLayout; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import agency.tango.materialintroscreen.R; + +public class ParallaxPercentRelativeLayout extends PercentRelativeLayout implements Parallaxable { + + public ParallaxPercentRelativeLayout(Context context) { + super(context); + } + + public ParallaxPercentRelativeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ParallaxPercentRelativeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected PercentRelativeLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + @Override + public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { + for (int i = getChildCount() - 1; i >= 0; i--) { + View child = getChildAt(i); + LayoutParams p = (LayoutParams) child.getLayoutParams(); + if (p.parallaxFactor == 0) + continue; + child.setTranslationX(getWidth() * -offset * p.parallaxFactor); + } + } + + public static class LayoutParams extends PercentRelativeLayout.LayoutParams { + float parallaxFactor = 0f; + + LayoutParams(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.mis_ParallaxLayout_Layout); + parallaxFactor = typedArray.getFloat(R.styleable.mis_ParallaxLayout_Layout_mis_layout_parallaxFactor, parallaxFactor); + typedArray.recycle(); + } + + LayoutParams(int width, int height) { + super(width, height); + } + + LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + @SuppressWarnings("unused") + LayoutParams(MarginLayoutParams source) { + super(source); + } + } +} diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxRelativeLayout.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxRelativeLayout.java index 1e524be..54025f2 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxRelativeLayout.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxRelativeLayout.java @@ -60,8 +60,8 @@ public static class LayoutParams extends RelativeLayout.LayoutParams { LayoutParams(Context context, AttributeSet attributeSet) { super(context, attributeSet); - TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); - parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); + TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.mis_ParallaxLayout_Layout); + parallaxFactor = typedArray.getFloat(R.styleable.mis_ParallaxLayout_Layout_mis_layout_parallaxFactor, parallaxFactor); typedArray.recycle(); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/InkPageIndicator.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/InkPageIndicator.java index 5fa7762..83a24fe 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/InkPageIndicator.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/InkPageIndicator.java @@ -1,3 +1,20 @@ +/* + * Copyright 2015 Google Inc. + * Modifications Copyright 2017 Tango Agency + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package agency.tango.materialintroscreen.widgets; import android.animation.Animator; @@ -24,21 +41,27 @@ import agency.tango.materialintroscreen.R; -public class InkPageIndicator extends View implements CustomViewPager.OnPageChangeListener, View.OnAttachStateChangeListener { +public class InkPageIndicator extends View + implements CustomViewPager.OnPageChangeListener, View.OnAttachStateChangeListener { + + // defaults private static final int DEFAULT_DOT_SIZE = 8; private static final int DEFAULT_GAP = 12; private static final int DEFAULT_ANIM_DURATION = 400; private static final int DEFAULT_UNSELECTED_COLOUR = 0x80ffffff; private static final int DEFAULT_SELECTED_COLOUR = 0xffffffff; + // constants private static final float INVALID_FRACTION = -1f; private static final float MINIMAL_REVEAL = 0.00001f; + private final Paint selectedPaint; private final Path unselectedDotPath; private final Path unselectedDotLeftPath; private final Path unselectedDotRightPath; private final RectF rectF; private final Interpolator interpolator; + float endX1; float endY1; float endX2; @@ -47,17 +70,24 @@ public class InkPageIndicator extends View implements CustomViewPager.OnPageChan float controlY1; float controlX2; float controlY2; + + // configurable attributes private int dotDiameter; private int gap; private long animDuration; private int unselectedColour; + + // derived from attributes private float dotRadius; private float halfDotRadius; private long animHalfDuration; private float dotTopY; private float dotCenterY; private float dotBottomY; + private SwipeableViewPager viewPager; + + // state private int pageCount; private int currentPage; private int previousPage; @@ -70,8 +100,12 @@ public class InkPageIndicator extends View implements CustomViewPager.OnPageChan private float[] dotRevealFractions; private boolean isAttachedToWindow; private boolean pageChanging; + + // drawing private Paint unselectedPaint; private Path combinedUnselectedPath; + + // animation private ValueAnimator moveAnimation; private PendingRetreatAnimator retreatAnimation; private PendingRevealAnimator[] revealAnimations; @@ -89,17 +123,27 @@ public InkPageIndicator(Context context, AttributeSet attrs, int defStyle) { final int density = (int) context.getResources().getDisplayMetrics().density; + // Load attributes final TypedArray typedArray = getContext().obtainStyledAttributes( - attrs, R.styleable.InkPageIndicator, defStyle, 0); + attrs, R.styleable.mis_InkPageIndicator, defStyle, 0); - dotDiameter = typedArray.getDimensionPixelSize(R.styleable.InkPageIndicator_dotDiameter, DEFAULT_DOT_SIZE * density); + dotDiameter = typedArray + .getDimensionPixelSize(R.styleable.mis_InkPageIndicator_mis_dotDiameter, + DEFAULT_DOT_SIZE * density); dotRadius = dotDiameter / 2; halfDotRadius = dotRadius / 2; - gap = typedArray.getDimensionPixelSize(R.styleable.InkPageIndicator_dotGap, DEFAULT_GAP * density); - animDuration = (long) typedArray.getInteger(R.styleable.InkPageIndicator_animationDuration, DEFAULT_ANIM_DURATION); + gap = typedArray.getDimensionPixelSize(R.styleable.mis_InkPageIndicator_mis_dotGap, + DEFAULT_GAP * density); + animDuration = (long) typedArray + .getInteger(R.styleable.mis_InkPageIndicator_mis_animationDuration, + DEFAULT_ANIM_DURATION); animHalfDuration = animDuration / 2; - unselectedColour = typedArray.getColor(R.styleable.InkPageIndicator_pageIndicatorColor, DEFAULT_UNSELECTED_COLOUR); - int selectedColour = typedArray.getColor(R.styleable.InkPageIndicator_currentPageIndicatorColor, DEFAULT_SELECTED_COLOUR); + unselectedColour = typedArray + .getColor(R.styleable.mis_InkPageIndicator_mis_pageIndicatorColor, + DEFAULT_UNSELECTED_COLOUR); + int selectedColour = typedArray + .getColor(R.styleable.mis_InkPageIndicator_mis_currentPageIndicatorColor, + DEFAULT_SELECTED_COLOUR); typedArray.recycle(); unselectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -108,6 +152,7 @@ public InkPageIndicator(Context context, AttributeSet attrs, int defStyle) { selectedPaint.setColor(selectedColour); interpolator = new FastOutSlowInInterpolator(); + // create paths & rect now – reuse & rewind later combinedUnselectedPath = new Path(); unselectedDotPath = new Path(); unselectedDotLeftPath = new Path(); @@ -144,6 +189,8 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse if (currentPosition != position) { fraction = 1f - positionOffset; + // when swiping from #2 to #1 ViewPager reports position as 1 and a descending offset + // need to convert this into our left-dot-based 'coordinate space' if (fraction == 1f) { leftDotPosition = Math.min(currentPosition, position); } @@ -156,8 +203,10 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse public void onPageSelected(int position) { if (position < pageCount) { if (isAttachedToWindow) { + // this is the main event we're interested in! setSelectedPage(position); } else { + // when not attached, don't animate the move, just store immediately setCurrentPageImmediate(); } } @@ -165,6 +214,7 @@ public void onPageSelected(int position) { @Override public void onPageScrollStateChanged(int state) { + // nothing to do } private void setPageCount(int pages) { @@ -206,7 +256,8 @@ private void setCurrentPageImmediate() { } private boolean isDotAnimationStarted() { - return dotCenterX != null && dotCenterX.length > 0 && (moveAnimation == null || !moveAnimation.isStarted()); + return dotCenterX != null && dotCenterX.length > 0 && (moveAnimation == null + || !moveAnimation.isStarted()); } private void resetState() { @@ -277,7 +328,9 @@ public void onViewDetachedFromWindow(View view) { @Override protected void onDraw(Canvas canvas) { - if (viewPager == null || pageCount == 0) return; + if (viewPager == null || pageCount == 0) { + return; + } drawUnselected(canvas); drawSelected(canvas); } @@ -285,6 +338,7 @@ protected void onDraw(Canvas canvas) { private void drawUnselected(Canvas canvas) { combinedUnselectedPath.rewind(); + // draw any settled, revealing or joining dots for (int page = 0; page < pageCount; page++) { int nextXIndex; @@ -304,6 +358,7 @@ private void drawUnselected(Canvas canvas) { combinedUnselectedPath.addPath(unselectedPath); } + // draw any retreating joins if (retreatingJoinX1 != INVALID_FRACTION) { Path retreatingJoinPath = getRetreatingJoinPath(); combinedUnselectedPath.addPath(retreatingJoinPath); @@ -312,20 +367,47 @@ private void drawUnselected(Canvas canvas) { canvas.drawPath(combinedUnselectedPath, unselectedPaint); } - private Path getUnselectedPath(int page, float centerX, float nextCenterX, float joiningFraction, float dotRevealFraction) { + /** + * Unselected dots can be in 6 states: + * + * #1 At rest + * #2 Joining neighbour, still separate + * #3 Joining neighbour, combined curved + * #4 Joining neighbour, combined straight + * #5 Join retreating + * #6 Dot re-showing / revealing + * + * It can also be in a combination of these states e.g. joining one neighbour while + * retreating from another. We therefore create a Path so that we can examine each + * dot pair separately and later take the union for these cases. + * + * This function returns a path for the given dot **and any action to it's right** e.g. joining + * or retreating from it's neighbour + */ + @SuppressWarnings("PMD.ExcessiveMethodLength") + private Path getUnselectedPath(int page, float centerX, float nextCenterX, + float joiningFraction, float dotRevealFraction) { unselectedDotPath.rewind(); + // case #1 – At rest if (isDotNotJoining(page, joiningFraction, dotRevealFraction)) { unselectedDotPath.addCircle(dotCenterX[page], dotCenterY, dotRadius, Path.Direction.CW); } if (isDotJoining(joiningFraction)) { + // case #2 – Joining neighbour, still separate + // start with the left dot unselectedDotLeftPath.rewind(); + + // start at the bottom center unselectedDotLeftPath.moveTo(centerX, dotBottomY); + + // semi circle to the top center rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); unselectedDotLeftPath.arcTo(rectF, 90, 180, true); + // cubic to the right middle endX1 = centerX + dotRadius + (joiningFraction * gap); endY1 = dotCenterY; controlX1 = centerX + halfDotRadius; @@ -336,6 +418,7 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float controlX2, controlY2, endX1, endY1); + // cubic back to the bottom center endX2 = centerX; endY2 = dotBottomY; controlX1 = endX1; @@ -348,12 +431,17 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float unselectedDotPath.addPath(unselectedDotLeftPath); + // now do the next dot to the right unselectedDotRightPath.rewind(); + + // start at the bottom center unselectedDotRightPath.moveTo(nextCenterX, dotBottomY); + // semi circle to the top center rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); unselectedDotRightPath.arcTo(rectF, 90, -180, true); + // cubic to the left middle endX1 = nextCenterX - dotRadius - (joiningFraction * gap); endY1 = dotCenterY; controlX1 = nextCenterX - halfDotRadius; @@ -364,6 +452,7 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float controlX2, controlY2, endX1, endY1); + // cubic back to the bottom center endX2 = nextCenterX; endY2 = dotBottomY; controlX1 = endX1; @@ -376,13 +465,21 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float unselectedDotPath.addPath(unselectedDotRightPath); } - if (joiningFraction > 0.5f && joiningFraction < 1f && retreatingJoinX1 == INVALID_FRACTION) { + if (joiningFraction > 0.5f && joiningFraction < 1f + && retreatingJoinX1 == INVALID_FRACTION) { + // case #3 – Joining neighbour, combined curved + + // adjust the fraction so that it goes from 0.3 -> 1 to produce a more realistic 'join' float adjustedFraction = (joiningFraction - 0.2f) * 1.25f; + // start in the bottom left unselectedDotPath.moveTo(centerX, dotBottomY); + + // semi-circle to the top left rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); unselectedDotPath.arcTo(rectF, 90, 180, true); + // bezier to the middle top of the join endX1 = centerX + dotRadius + (gap / 2); endY1 = dotCenterY - (adjustedFraction * dotRadius); controlX1 = endX1 - (adjustedFraction * dotRadius); @@ -393,6 +490,7 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float controlX2, controlY2, endX1, endY1); + // bezier to the top right of the join endX2 = nextCenterX; endY2 = dotTopY; controlX1 = endX1 + ((1 - adjustedFraction) * dotRadius); @@ -403,9 +501,12 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float controlX2, controlY2, endX2, endY2); + // semi-circle to the bottom right rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); unselectedDotPath.arcTo(rectF, 270, 180, true); + // bezier to the middle bottom of the join + // endX1 stays the same endY1 = dotCenterY + (adjustedFraction * dotRadius); controlX1 = endX1 + (adjustedFraction * dotRadius); controlY1 = dotBottomY; @@ -415,6 +516,7 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float controlX2, controlY2, endX1, endY1); + // bezier back to the start point in the bottom left endX2 = centerX; endY2 = dotBottomY; controlX1 = endX1 - ((1 - adjustedFraction) * dotRadius); @@ -426,11 +528,19 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float endX2, endY2); } if (joiningFraction == 1 && retreatingJoinX1 == INVALID_FRACTION) { + // case #4 Joining neighbour, combined straight technically we could use case 3 for this + // situation as well but assume that this is an optimization rather than faffing around + // with beziers just to draw a rounded rect rectF.set(centerX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW); } + // case #5 is handled by #getRetreatingJoinPath() + // this is done separately so that we can have a single retreating path spanning + // multiple dots and therefore animate it's movement smoothly + if (dotRevealFraction > MINIMAL_REVEAL) { + // case #6 – previously hidden dot revealing unselectedDotPath.addCircle(centerX, dotCenterY, dotRevealFraction * dotRadius, Path.Direction.CW); } @@ -439,7 +549,8 @@ private Path getUnselectedPath(int page, float centerX, float nextCenterX, float } private boolean isDotJoining(float joiningFraction) { - return joiningFraction > 0f && joiningFraction <= 0.5f && retreatingJoinX1 == INVALID_FRACTION; + return joiningFraction > 0f && joiningFraction <= 0.5f + && retreatingJoinX1 == INVALID_FRACTION; } private boolean isDotNotJoining(int page, float joiningFraction, float dotRevealFraction) { @@ -481,14 +592,20 @@ private void setSelectedPage(int now) { } } + // create the anim to move the selected dot – this animator will kick off + // retreat animations when it has moved 75% of the way. + // The retreat animation in turn will kick of reveal anims when the + // retreat has passed any dots to be revealed moveAnimation = createMoveSelectedAnimator(dotCenterX[now], previousPage, now, steps); moveAnimation.start(); } private ValueAnimator createMoveSelectedAnimator( final float moveTo, int was, int now, int steps) { + // create the actual move animator ValueAnimator moveSelected = ValueAnimator.ofFloat(selectedDotX, moveTo); + // also set up a pending retreat anim – this starts when the move is 75% complete retreatAnimation = new PendingRetreatAnimator(was, now, steps, now > was ? new RightwardStartPredicate(moveTo - ((moveTo - selectedDotX) * 0.25f)) : @@ -511,15 +628,21 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { moveSelected.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { + // set a flag so that we continue to draw the unselected dot in the target position + // until the selected dot has finished moving into place selectedDotInPosition = false; } @Override public void onAnimationEnd(Animator animation) { + // set a flag when anim finishes so that we don't draw both selected & unselected + // page dots selectedDotInPosition = true; } }); + // slightly delay the start to give the joins a chance to run + // unless dot isn't in position yet – then don't delay! moveSelected.setStartDelay(selectedDotInPosition ? animDuration / 4L : 0L); moveSelected.setDuration(animDuration * 3L / 4L); moveSelected.setInterpolator(interpolator); @@ -527,11 +650,9 @@ public void onAnimationEnd(Animator animation) { } private void setJoiningFraction(int leftDot, float fraction) { - if (joiningFractions != null) { - if (leftDot < joiningFractions.length) { - joiningFractions[leftDot] = fraction; - ViewCompat.postInvalidateOnAnimation(this); - } + if (joiningFractions != null && leftDot < joiningFractions.length) { + joiningFractions[leftDot] = fraction; + ViewCompat.postInvalidateOnAnimation(this); } } @@ -570,6 +691,7 @@ public Parcelable onSaveInstanceState() { } static class SavedState extends BaseSavedState { + public static final Creator CREATOR = new Creator() { @Override public SavedState createFromParcel(Parcel in) { @@ -599,7 +721,12 @@ public void writeToParcel(Parcel dest, int flags) { } } + + /** + * A {@link ValueAnimator} that starts once a given predicate returns true. + */ public abstract class PendingStartAnimator extends ValueAnimator { + boolean hasStarted; StartPredicate predicate; @@ -617,7 +744,13 @@ void startIfNecessary(float currentValue) { } } + /** + * An Animator that shows and then shrinks a retreating join between the previous and newly + * selected pages. This also sets up some pending dot reveals – to be started when the retreat + * has passed the dot to be revealed. + */ public class PendingRetreatAnimator extends PendingStartAnimator { + PendingRetreatAnimator(int was, int now, int steps, StartPredicate predicate) { super(predicate); setDuration(animHalfDuration); @@ -701,7 +834,11 @@ public void onAnimationEnd(Animator animation) { } } + /** + * An Animator that animates a given dot's revealFraction i.e. scales it up + */ public class PendingRevealAnimator extends PendingStartAnimator { + private int dot; PendingRevealAnimator(int dot, StartPredicate predicate) { @@ -727,7 +864,11 @@ public void onAnimationEnd(Animator animation) { } } + /** + * A predicate used to start an animation when a test passes + */ public abstract class StartPredicate { + float thresholdValue; StartPredicate(float thresholdValue) { @@ -737,7 +878,11 @@ public abstract class StartPredicate { abstract boolean shouldStart(float currentValue); } + /** + * A predicate used to start an animation when a given value is greater than a threshold + */ public class RightwardStartPredicate extends StartPredicate { + RightwardStartPredicate(float thresholdValue) { super(thresholdValue); } @@ -747,7 +892,11 @@ boolean shouldStart(float currentValue) { } } + /** + * A predicate used to start an animation then a given value is less than a threshold + */ public class LeftwardStartPredicate extends StartPredicate { + LeftwardStartPredicate(float thresholdValue) { super(thresholdValue); } diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/OverScrollViewPager.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/OverScrollViewPager.java index 186d532..0bceb18 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/OverScrollViewPager.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/OverScrollViewPager.java @@ -14,6 +14,7 @@ import agency.tango.materialintroscreen.listeners.IFinishListener; public class OverScrollViewPager extends RelativeLayout { + private SwipeableViewPager swipeableViewPager = null; private boolean mIsBeingDragged = false; private float mMotionBeginX = 0; @@ -41,6 +42,7 @@ public OverScrollViewPager(Context context, AttributeSet attrs, int defStyle) { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } + @SuppressWarnings({"PMD.CollapsibleIfStatements"}) @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); @@ -95,10 +97,11 @@ private void moveOverScrollView(float currentX) { scrollTo((int) -currentX, 0); positionOffset = calculateOffset(); - swipeableViewPager.onPageScrolled(swipeableViewPager.getAdapter().getLastItemPosition(), positionOffset, 0); + swipeableViewPager.onPageScrolled(swipeableViewPager.getAdapter().getLastItemPosition(), + positionOffset, 0); if (shouldFinish()) { - finishListener.doOnFinish(); + finishListener.onFinish(); } } } @@ -120,14 +123,17 @@ private void resetOverScrollViewWithAnimation(final float currentX) { } private void finishOverScrollViewWithAnimation(float currentX) { - post(new SmoothScrollRunnable((int) currentX, -getWidth(), 300, new AccelerateInterpolator())); + post(new SmoothScrollRunnable((int) currentX, -getWidth(), 300, + new AccelerateInterpolator())); } + @SuppressWarnings({"PMD.SimplifyBooleanReturns", "RedundantIfStatement", "PMD.CollapsibleIfStatements"}) private boolean canOverScrollAtEnd() { SwipeableViewPager viewPager = getOverScrollView(); PagerAdapter adapter = viewPager.getAdapter(); if (null != adapter && adapter.getCount() > 0) { - if (viewPager.alphaExitTransitionEnabled() && viewPager.getCurrentItem() == adapter.getCount() - 1) { + if (viewPager.alphaExitTransitionEnabled() + && viewPager.getCurrentItem() == adapter.getCount() - 1) { return true; } return false; @@ -138,11 +144,12 @@ private boolean canOverScrollAtEnd() { private SwipeableViewPager createOverScrollView() { SwipeableViewPager swipeableViewPager = new SwipeableViewPager(getContext(), null); - swipeableViewPager.setId(R.id.swipeable_view_pager); + swipeableViewPager.setId(R.id.mis_swipeable_view_pager); return swipeableViewPager; } final class SmoothScrollRunnable implements Runnable { + private final Interpolator interpolator; private final int scrollToPosition; private final int scrollFromPosition; @@ -151,7 +158,8 @@ final class SmoothScrollRunnable implements Runnable { private long startTime = -1; private int currentPosition = -1; - SmoothScrollRunnable(int fromPosition, int toPosition, long duration, Interpolator scrollAnimationInterpolator) { + SmoothScrollRunnable(int fromPosition, int toPosition, long duration, + Interpolator scrollAnimationInterpolator) { scrollFromPosition = fromPosition; scrollToPosition = toPosition; interpolator = scrollAnimationInterpolator; diff --git a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/SwipeableViewPager.java b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/SwipeableViewPager.java index dc1d9c1..13fa04d 100644 --- a/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/SwipeableViewPager.java +++ b/material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/SwipeableViewPager.java @@ -7,13 +7,17 @@ import android.view.KeyEvent; import android.view.MotionEvent; +import agency.tango.materialintroscreen.ISlideErrorHandler; import agency.tango.materialintroscreen.adapter.SlidesAdapter; +@SuppressWarnings("PMD.SingularField") public class SwipeableViewPager extends CustomViewPager { + private float startPos = 0; private int currentIt; private boolean swipingAllowed; private boolean alphaExitTransitionEnabled = false; + private ISlideErrorHandler errorHandler; public SwipeableViewPager(Context context, AttributeSet attrs) { super(context, attrs); @@ -53,13 +57,15 @@ public boolean onTouchEvent(final MotionEvent event) { resolveSwipingRightAllowed(); return super.onTouchEvent(event); case (MotionEvent.ACTION_MOVE): - if (!swipingAllowed && startPos - event.getX() > 16) { + if (isSwipingNotAllowed(event)) { + errorHandler.handleError(); return true; } return super.onTouchEvent(event); case (MotionEvent.ACTION_UP): - if (!swipingAllowed && startPos - event.getX() > 16) { + if (isSwipingNotAllowed(event)) { smoothScrollTo(getWidth() * currentIt, 0); + errorHandler.handleError(); return true; } startPos = 0; @@ -79,8 +85,11 @@ public boolean executeKeyEvent(KeyEvent event) { return false; } - public void moveToNextPage() - { + public void registerSlideErrorHandler(ISlideErrorHandler handler) { + errorHandler = handler; + } + + public void moveToNextPage() { setCurrentItem(getCurrentItem() + 1, true); } @@ -104,6 +113,10 @@ public boolean alphaExitTransitionEnabled() { return alphaExitTransitionEnabled && swipingAllowed; } + private boolean isSwipingNotAllowed(MotionEvent event) { + return !swipingAllowed && startPos - event.getX() > 16; + } + private void resolveSwipingRightAllowed() { if (getAdapter().shouldLockSlide(getCurrentItem())) { setSwipingRightAllowed(false); diff --git a/material-intro-screen/src/main/java/android/support/v4/view/CustomViewPager.java b/material-intro-screen/src/main/java/android/support/v4/view/CustomViewPager.java index 5164dad..6ad12d9 100644 --- a/material-intro-screen/src/main/java/android/support/v4/view/CustomViewPager.java +++ b/material-intro-screen/src/main/java/android/support/v4/view/CustomViewPager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 The Android Open Source Project + * Modifications Copyright 2017 Tango Agency * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,9 +71,9 @@ * through pages of data. You supply an implementation of a * {@link PagerAdapter} to generate the pages that the view shows. * - *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, + *

CustomViewPager is most often used in conjunction with {@link android.app.Fragment}, * which is a convenient way to supply and manage the lifecycle of each page. - * There are standard adapters implemented for using fragments with the ViewPager, + * There are standard adapters implemented for using fragments with the CustomViewPager, * which cover the most common use cases. These are * {@link android.support.v4.app.FragmentPagerAdapter} and * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these @@ -84,7 +85,7 @@ * its {@code android:layout_gravity} attribute. For example: * *

- * <android.support.v4.view.ViewPager
+ * <android.support.v4.view.CustomViewPager
  *     android:layout_width="match_parent"
  *     android:layout_height="match_parent">
  *
@@ -93,21 +94,24 @@
  *         android:layout_height="wrap_content"
  *         android:layout_gravity="top" />
  *
- * </android.support.v4.view.ViewPager>
+ * </android.support.v4.view.CustomViewPager>
  * 
* - *

For more information about how to use ViewPager, read For more information about how to use CustomViewPager, read Creating Swipe Views with * Tabs.

* - *

Below is a more complicated example of ViewPager, using it in conjunction + *

Below is a more complicated example of CustomViewPager, using it in conjunction * with {@link android.app.ActionBar} tabs. You can find other examples of using - * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. + * CustomViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. * * {@sample frameworks/support/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java - * complete} + * complete} */ + +@SuppressWarnings("PMD") public class CustomViewPager extends ViewGroup { + private static final String TAG = "CustomViewPager"; private static final boolean DEBUG = false; @@ -121,7 +125,7 @@ public class CustomViewPager extends ViewGroup { private static final int MIN_FLING_VELOCITY = 400; // dips - private static final int[] LAYOUT_ATTRS = new int[] { + private static final int[] LAYOUT_ATTRS = new int[]{ android.R.attr.layout_gravity }; @@ -131,21 +135,14 @@ public class CustomViewPager extends ViewGroup { */ private int mExpectedAdapterCount; - static class ItemInfo { - Object object; - int position; - boolean scrolling; - float widthFactor; - float offset; - } - - private static final Comparator COMPARATOR = new Comparator(){ + private static final Comparator COMPARATOR = new Comparator() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { return lhs.position - rhs.position; } }; + @SuppressWarnings("PMD.AvoidReassigningParameters") private static final Interpolator sInterpolator = new Interpolator() { @Override public float getInterpolation(float t) { @@ -181,8 +178,6 @@ public float getInterpolation(float t) { private float mFirstOffset = -Float.MAX_VALUE; private float mLastOffset = Float.MAX_VALUE; - private int mChildWidthMeasureSpec; - private int mChildHeightMeasureSpec; private boolean mInLayout; private boolean mScrollingCacheEnabled; @@ -234,7 +229,6 @@ public float getInterpolation(float t) { private EdgeEffectCompat mRightEdge; private boolean mFirstLayout = true; - private boolean mNeedCalculatePageOffsets = false; private boolean mCalledSuper; private int mDecorChildCount; @@ -287,9 +281,11 @@ public interface OnPageChangeListener { * This method will be invoked when the current page is scrolled, either as part * of a programmatically initiated smooth scroll or a user initiated touch scroll. * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param positionOffset Value from [0, 1) indicating the offset from the page at position. + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is + * nonzero. + * @param positionOffset Value from [0, 1) indicating the offset from the page at + * position. * @param positionOffsetPixels Value in pixels indicating the offset from position. */ void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); @@ -321,6 +317,7 @@ public interface OnPageChangeListener { * every method of {@link OnPageChangeListener}. */ public static class SimpleOnPageChangeListener implements OnPageChangeListener { + @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // This space for rent @@ -347,10 +344,11 @@ public void onPageScrollStateChanged(int state) { * be ignored.

*/ public interface PageTransformer { + /** * Apply a property transformation to the given page. * - * @param page Apply the transformation to this page + * @param page Apply the transformation to this page * @param position Position of page relative to the current front-and-center * position of the pager. 0 is front and center. 1 is one full * page position to the right, and -1 is one page position to the left. @@ -362,6 +360,7 @@ public interface PageTransformer { * Callback interface for responding to adapter changes. */ public interface OnAdapterChangeListener { + /** * Called when the adapter for the given view pager has changed. * @@ -370,7 +369,7 @@ public interface OnAdapterChangeListener { * @param newAdapter the newly set adapter */ void onAdapterChanged(@NonNull CustomViewPager viewPager, - @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter); + @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter); } /** @@ -387,6 +386,7 @@ void onAdapterChanged(@NonNull CustomViewPager viewPager, @Target(ElementType.TYPE) @Inherited public @interface DecorView { + } public CustomViewPager(Context context) { @@ -432,7 +432,7 @@ void initViewPager() { @Override public WindowInsetsCompat onApplyWindowInsets(final View v, - final WindowInsetsCompat originalInsets) { + final WindowInsetsCompat originalInsets) { // First let the ViewPager itself try and consume them... final WindowInsetsCompat applied = ViewCompat.onApplyWindowInsets(v, originalInsets); @@ -616,7 +616,7 @@ public void setCurrentItem(int item) { /** * Set the currently selected page. * - * @param item Item index to select + * @param item Item index to select * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately */ public void setCurrentItem(int item, boolean smoothScroll) { @@ -673,7 +673,7 @@ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int } private void scrollToItem(int item, boolean smoothScroll, int velocity, - boolean dispatchSelected) { + boolean dispatchSelected) { final ItemInfo curInfo = infoForPosition(item); int destX = 0; if (curInfo != null) { @@ -701,7 +701,6 @@ private void scrollToItem(int item, boolean smoothScroll, int velocity, * scrolled. See {@link OnPageChangeListener}. * * @param listener Listener to set - * * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)} * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead. */ @@ -754,11 +753,12 @@ public void clearOnPageChangeListeners() { * transformations to each page, overriding the default sliding look and feel. * *

Note: Prior to Android 3.0 the property animation APIs did not exist. - * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

+ * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no + * effect.

* * @param reverseDrawingOrder true if the supplied PageTransformer requires page views * to be drawn from last to first instead of first to last. - * @param transformer PageTransformer that will modify each page's animation properties + * @param transformer PageTransformer that will modify each page's animation properties */ public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { if (Build.VERSION.SDK_INT >= 11) { @@ -771,7 +771,9 @@ public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer tran } else { mDrawingOrder = DRAW_ORDER_DEFAULT; } - if (needsPopulate) populate(); + if (needsPopulate) { + populate(); + } } } @@ -780,7 +782,7 @@ void setChildrenDrawingOrderEnabledCompat(boolean enable) { if (mSetChildrenDrawingOrderEnabled == null) { try { mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( - "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); + "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE}); } catch (NoSuchMethodException e) { Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); } @@ -887,7 +889,9 @@ public int getPageMargin() { */ public void setPageMarginDrawable(Drawable d) { mMarginDrawable = d; - if (d != null) refreshDrawableState(); + if (d != null) { + refreshDrawableState(); + } setWillNotDraw(d == null); invalidate(); } @@ -938,8 +942,8 @@ public void smoothScrollTo(int x, int y) { /** * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. * - * @param x the number of pixels to scroll by on the X axis - * @param y the number of pixels to scroll by on the Y axis + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) */ void smoothScrollTo(int x, int y, int velocity) { @@ -1105,7 +1109,9 @@ void populate(int newCurrentItem) { // fling to a new position until we have finished the scroll to // that position, avoiding glitches from happening at that point. if (mPopulatePending) { - if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); + if (DEBUG) { + Log.i(TAG, "populate is pending, skipping for now..."); + } sortChildDrawingOrder(); return; } @@ -1145,7 +1151,9 @@ void populate(int newCurrentItem) { for (curIndex = 0; curIndex < mItems.size(); curIndex++) { final ItemInfo ii = mItems.get(curIndex); if (ii.position >= mCurItem) { - if (ii.position == mCurItem) curItem = ii; + if (ii.position == mCurItem) { + curItem = ii; + } break; } } @@ -1303,7 +1311,7 @@ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCu ItemInfo ii = null; float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; for (int pos = oldCurPosition + 1; - pos <= curItem.position && itemIndex < mItems.size(); pos++) { + pos <= curItem.position && itemIndex < mItems.size(); pos++) { ii = mItems.get(itemIndex); while (pos > ii.position && itemIndex < mItems.size() - 1) { itemIndex++; @@ -1323,7 +1331,7 @@ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCu ItemInfo ii = null; float offset = oldCurInfo.offset; for (int pos = oldCurPosition - 1; - pos >= curItem.position && itemIndex >= 0; pos--) { + pos >= curItem.position && itemIndex >= 0; pos--) { ii = mItems.get(itemIndex); while (pos < ii.position && itemIndex > 0) { itemIndex--; @@ -1356,7 +1364,9 @@ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCu } offset -= ii.widthFactor + marginOffset; ii.offset = offset; - if (ii.position == 0) mFirstOffset = offset; + if (ii.position == 0) { + mFirstOffset = offset; + } } offset = curItem.offset + curItem.widthFactor + marginOffset; pos = curItem.position + 1; @@ -1373,7 +1383,6 @@ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCu offset += ii.widthFactor + marginOffset; } - mNeedCalculatePageOffsets = false; } /** @@ -1383,6 +1392,7 @@ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCu * contains that state. */ public static class SavedState extends AbsSavedState { + int position; Parcelable adapterState; ClassLoader loader; @@ -1411,6 +1421,7 @@ public String toString() { public SavedState createFromParcel(Parcel in, ClassLoader loader) { return new SavedState(in, loader); } + @Override public SavedState[] newArray(int size) { return new SavedState[size]; @@ -1606,8 +1617,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } } - mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); - mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); + int childWidthMeasureSpec = MeasureSpec + .makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); + int childHeightMeasureSpec = MeasureSpec + .makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); // Make sure we have created all fragments that we need to have shown. mInLayout = true; @@ -1620,14 +1633,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { if (DEBUG) { - Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec); + Log.v(TAG, "Measuring #" + i + " " + child + ": " + childWidthMeasureSpec); } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp == null || !lp.isDecor) { final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); - child.measure(widthSpec, mChildHeightMeasureSpec); + child.measure(widthSpec, childHeightMeasureSpec); } } } @@ -1846,9 +1859,9 @@ private boolean pageScrolled(int xpos) { * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled * returns. * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param offset Value from [0, 1) indicating the offset from the page at position. + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * @param offset Value from [0, 1) indicating the offset from the page at position. * @param offsetPixels Value in pixels indicating the offset from position. */ @CallSuper @@ -1863,7 +1876,9 @@ protected void onPageScrolled(int position, float offset, int offsetPixels) { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) continue; + if (!lp.isDecor) { + continue; + } final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; int childLeft = 0; @@ -1902,7 +1917,9 @@ protected void onPageScrolled(int position, float offset, int offsetPixels) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.isDecor) continue; + if (lp.isDecor) { + continue; + } final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); mPageTransformer.transformPage(child, transformPos); } @@ -2025,7 +2042,9 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { // Always take care of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the drag. - if (DEBUG) Log.v(TAG, "Intercept done!"); + if (DEBUG) { + Log.v(TAG, "Intercept done!"); + } resetTouch(); return false; } @@ -2034,11 +2053,15 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { // are dragging. if (action != MotionEvent.ACTION_DOWN) { if (mIsBeingDragged) { - if (DEBUG) Log.v(TAG, "Intercept returning true!"); + if (DEBUG) { + Log.v(TAG, "Intercept returning true!"); + } return true; } if (mIsUnableToDrag) { - if (DEBUG) Log.v(TAG, "Intercept returning false!"); + if (DEBUG) { + Log.v(TAG, "Intercept returning false!"); + } return false; } } @@ -2066,7 +2089,9 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { final float xDiff = Math.abs(dx); final float y = ev.getY(pointerIndex); final float yDiff = Math.abs(y - mInitialMotionY); - if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); + if (DEBUG) { + Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); + } if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && canScroll(this, false, (int) dx, (int) x, (int) y)) { @@ -2077,7 +2102,9 @@ && canScroll(this, false, (int) dx, (int) x, (int) y)) { return false; } if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); + if (DEBUG) { + Log.v(TAG, "Starting drag!"); + } mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); @@ -2090,7 +2117,9 @@ && canScroll(this, false, (int) dx, (int) x, (int) y)) { // direction to be counted as a drag... abort // any attempt to drag horizontally, to work correctly // with children that have scrolling containers. - if (DEBUG) Log.v(TAG, "Starting unable to drag!"); + if (DEBUG) { + Log.v(TAG, "Starting unable to drag!"); + } mIsUnableToDrag = true; } if (mIsBeingDragged) { @@ -2210,7 +2239,9 @@ public boolean onTouchEvent(MotionEvent ev) { Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); } if (xDiff > mTouchSlop && xDiff > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); + if (DEBUG) { + Log.v(TAG, "Starting drag!"); + } mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : @@ -2346,7 +2377,7 @@ private boolean performDrag(float x) { /** * @return Info about the page at the current scroll position. - * This can be synthetic for a missing middle page; the 'object' field can be null. + * This can be synthetic for a missing middle page; the 'object' field can be null. */ private ItemInfo infoForCurrentScrollPosition() { final int width = getClientWidth(); @@ -2509,7 +2540,6 @@ protected void onDraw(Canvas canvas) { * is already in progress, this method will return false. * * @return true if the fake drag began successfully, false if it could not be started. - * * @see #fakeDragBy(float) * @see #endFakeDrag() */ @@ -2621,7 +2651,6 @@ public void fakeDragBy(float xOffset) { * Returns true if a fake drag is in progress. * * @return true if currently in a fake drag, false otherwise. - * * @see #beginFakeDrag() * @see #fakeDragBy(float) * @see #endFakeDrag() @@ -2675,7 +2704,7 @@ private void setScrollingCacheEnabled(boolean enabled) { * * @param direction Negative to check scrolling left, positive to check scrolling right. * @return Whether this ViewPager can be scrolled in the specified direction. It will always - * return false if the specified direction is 0. + * return false if the specified direction is 0. */ public boolean canScrollHorizontally(int direction) { if (mAdapter == null) { @@ -2696,12 +2725,12 @@ public boolean canScrollHorizontally(int direction) { /** * Tests scrollability within child views of v given a delta of dx. * - * @param v View to test for horizontal scrollability + * @param v View to test for horizontal scrollability * @param checkV Whether the view v passed should itself be checked for scrollability (true), * or just its children (false). - * @param dx Delta scrolled in pixels - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point + * @param dx Delta scrolled in pixels + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point * @return true if child views of v can be scrolled by delta of dx. */ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { @@ -2781,7 +2810,7 @@ public boolean arrowScroll(int direction) { } else if (currentFocused != null) { boolean isChild = false; for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { + parent = parent.getParent()) { if (parent == this) { isChild = true; break; @@ -2792,7 +2821,7 @@ public boolean arrowScroll(int direction) { final StringBuilder sb = new StringBuilder(); sb.append(currentFocused.getClass().getSimpleName()); for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { + parent = parent.getParent()) { sb.append(" => ").append(parent.getClass().getSimpleName()); } Log.e(TAG, "arrowScroll tried to find focus based on non-child " @@ -2948,7 +2977,7 @@ public void addTouchables(ArrayList views) { */ @Override protected boolean onRequestFocusInDescendants(int direction, - Rect previouslyFocusedRect) { + Rect previouslyFocusedRect) { int index; int increment; int end; @@ -3060,13 +3089,15 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) { setCurrentItem(mCurItem + 1); return true; } - } return false; + } + return false; case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { if (canScrollHorizontally(-1)) { setCurrentItem(mCurItem - 1); return true; } - } return false; + } + return false; } return false; } @@ -3077,10 +3108,12 @@ private boolean canScroll() { } private class PagerObserver extends DataSetObserver { + @Override public void onChanged() { dataSetChanged(); } + @Override public void onInvalidated() { dataSetChanged(); @@ -3092,6 +3125,7 @@ public void onInvalidated() { * ViewPager. */ public static class LayoutParams extends ViewGroup.LayoutParams { + /** * true if this view is a decoration on the pager itself and not * a view supplied by the adapter. @@ -3140,6 +3174,7 @@ public LayoutParams(Context context, AttributeSet attrs) { } static class ViewPositionComparator implements Comparator { + @Override public int compare(View lhs, View rhs) { final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); @@ -3150,4 +3185,13 @@ public int compare(View lhs, View rhs) { return llp.position - rlp.position; } } + + static class ItemInfo { + + Object object; + int position; + boolean scrolling; + float widthFactor; + float offset; + } } \ No newline at end of file diff --git a/material-intro-screen/src/main/res/anim/cycle_2.xml b/material-intro-screen/src/main/res/anim/mis_cycle_2.xml similarity index 100% rename from material-intro-screen/src/main/res/anim/cycle_2.xml rename to material-intro-screen/src/main/res/anim/mis_cycle_2.xml diff --git a/material-intro-screen/src/main/res/anim/fade_in.xml b/material-intro-screen/src/main/res/anim/mis_fade_in.xml similarity index 100% rename from material-intro-screen/src/main/res/anim/fade_in.xml rename to material-intro-screen/src/main/res/anim/mis_fade_in.xml diff --git a/material-intro-screen/src/main/res/anim/fade_out.xml b/material-intro-screen/src/main/res/anim/mis_fade_out.xml similarity index 100% rename from material-intro-screen/src/main/res/anim/fade_out.xml rename to material-intro-screen/src/main/res/anim/mis_fade_out.xml diff --git a/material-intro-screen/src/main/res/anim/shake_it.xml b/material-intro-screen/src/main/res/anim/mis_shake_it.xml similarity index 80% rename from material-intro-screen/src/main/res/anim/shake_it.xml rename to material-intro-screen/src/main/res/anim/mis_shake_it.xml index 12fc9f9..c4a07f5 100644 --- a/material-intro-screen/src/main/res/anim/shake_it.xml +++ b/material-intro-screen/src/main/res/anim/mis_shake_it.xml @@ -2,5 +2,5 @@ \ No newline at end of file diff --git a/material-intro-screen/src/main/res/drawable-v21/button_background.xml b/material-intro-screen/src/main/res/drawable-v21/mis_button_background.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable-v21/button_background.xml rename to material-intro-screen/src/main/res/drawable-v21/mis_button_background.xml diff --git a/material-intro-screen/src/main/res/drawable/button_background.xml b/material-intro-screen/src/main/res/drawable/mis_button_background.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable/button_background.xml rename to material-intro-screen/src/main/res/drawable/mis_button_background.xml diff --git a/material-intro-screen/src/main/res/drawable/ic_finish.xml b/material-intro-screen/src/main/res/drawable/mis_ic_finish.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable/ic_finish.xml rename to material-intro-screen/src/main/res/drawable/mis_ic_finish.xml diff --git a/material-intro-screen/src/main/res/drawable/ic_next.xml b/material-intro-screen/src/main/res/drawable/mis_ic_next.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable/ic_next.xml rename to material-intro-screen/src/main/res/drawable/mis_ic_next.xml diff --git a/material-intro-screen/src/main/res/drawable/ic_previous.xml b/material-intro-screen/src/main/res/drawable/mis_ic_previous.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable/ic_previous.xml rename to material-intro-screen/src/main/res/drawable/mis_ic_previous.xml diff --git a/material-intro-screen/src/main/res/drawable/ic_skip.xml b/material-intro-screen/src/main/res/drawable/mis_ic_skip.xml similarity index 100% rename from material-intro-screen/src/main/res/drawable/ic_skip.xml rename to material-intro-screen/src/main/res/drawable/mis_ic_skip.xml diff --git a/material-intro-screen/src/main/res/layout-land/fragment_slide.xml b/material-intro-screen/src/main/res/layout-land/mis_fragment_slide.xml similarity index 94% rename from material-intro-screen/src/main/res/layout-land/fragment_slide.xml rename to material-intro-screen/src/main/res/layout-land/mis_fragment_slide.xml index 98ee8d2..4e66dc0 100644 --- a/material-intro-screen/src/main/res/layout-land/fragment_slide.xml +++ b/material-intro-screen/src/main/res/layout-land/mis_fragment_slide.xml @@ -16,13 +16,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:layout_marginRight="16dp" android:layout_marginBottom="128dp" - android:paddingTop="32dp" + android:layout_marginRight="16dp" android:layout_weight="1" android:adjustViewBounds="true" + android:paddingTop="32dp" android:visibility="gone" - app:layout_parallaxFactor="0.6" + app:mis_layout_parallaxFactor="0.6" tools:src="@android:drawable/arrow_down_float" tools:visibility="visible" /> @@ -39,7 +39,6 @@ android:layout_height="wrap_content" android:gravity="center" android:textAppearance="@style/TextAppearance.AppCompat.Headline" - app:layout_parallaxFactor="0.5" tools:text="Lorem ipsum" />
diff --git a/material-intro-screen/src/main/res/layout/empty_fragment_slide.xml b/material-intro-screen/src/main/res/layout/empty_fragment_slide.xml deleted file mode 100644 index ca62b3a..0000000 --- a/material-intro-screen/src/main/res/layout/empty_fragment_slide.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/material-intro-screen/src/main/res/layout/activity_material_intro.xml b/material-intro-screen/src/main/res/layout/mis_activity_material_intro.xml similarity index 79% rename from material-intro-screen/src/main/res/layout/activity_material_intro.xml rename to material-intro-screen/src/main/res/layout/mis_activity_material_intro.xml index 0826b95..8af0cc1 100644 --- a/material-intro-screen/src/main/res/layout/activity_material_intro.xml +++ b/material-intro-screen/src/main/res/layout/mis_activity_material_intro.xml @@ -23,14 +23,14 @@ android:layout_gravity="bottom" android:orientation="vertical" android:paddingTop="32dp" - app:layout_behavior="agency.tango.materialintroscreen.MoveUpBehaviour"> + app:layout_behavior="agency.tango.materialintroscreen.behaviours.MoveUpBehaviour"> @@ -46,9 +46,9 @@ android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:layout_marginLeft="16dp" - android:background="@drawable/button_background" + android:background="@drawable/mis_button_background" android:padding="16dp" - android:src="@drawable/ic_previous" + android:src="@drawable/mis_ic_previous" android:visibility="invisible" /> + app:mis_animationDuration="320" + app:mis_currentPageIndicatorColor="@android:color/white" + app:mis_dotDiameter="8dp" + app:mis_dotGap="8dp" + app:mis_pageIndicatorColor="@android:color/holo_red_light" /> + android:src="@drawable/mis_ic_next" /> diff --git a/material-intro-screen/src/main/res/layout/fragment_slide.xml b/material-intro-screen/src/main/res/layout/mis_fragment_slide.xml similarity index 93% rename from material-intro-screen/src/main/res/layout/fragment_slide.xml rename to material-intro-screen/src/main/res/layout/mis_fragment_slide.xml index b8ed075..983ca3f 100644 --- a/material-intro-screen/src/main/res/layout/fragment_slide.xml +++ b/material-intro-screen/src/main/res/layout/mis_fragment_slide.xml @@ -26,7 +26,7 @@ android:layout_weight="1" android:adjustViewBounds="true" android:gravity="center" - app:layout_parallaxFactor="0.6" + app:mis_layout_parallaxFactor="0.6" tools:src="@android:drawable/sym_action_call" /> diff --git a/material-intro-screen/src/main/res/values-v21/styles.xml b/material-intro-screen/src/main/res/values-v21/styles.xml index a5663b4..dce9a94 100644 --- a/material-intro-screen/src/main/res/values-v21/styles.xml +++ b/material-intro-screen/src/main/res/values-v21/styles.xml @@ -1,13 +1,13 @@ - + - - diff --git a/material-intro-screen/src/main/res/values/attrs.xml b/material-intro-screen/src/main/res/values/attrs.xml index 4dcfc69..59921f4 100644 --- a/material-intro-screen/src/main/res/values/attrs.xml +++ b/material-intro-screen/src/main/res/values/attrs.xml @@ -1,13 +1,13 @@ - - - - - - + + + + + + - - + + \ No newline at end of file diff --git a/material-intro-screen/src/main/res/values/colors.xml b/material-intro-screen/src/main/res/values/colors.xml index 6a195f9..f77531b 100644 --- a/material-intro-screen/src/main/res/values/colors.xml +++ b/material-intro-screen/src/main/res/values/colors.xml @@ -1,5 +1,6 @@ - @android:color/transparent - + @android:color/transparent + #3F51B5 + #1A237E \ No newline at end of file diff --git a/material-intro-screen/src/main/res/values/dimens.xml b/material-intro-screen/src/main/res/values/dimens.xml index a4d05b0..c178919 100644 --- a/material-intro-screen/src/main/res/values/dimens.xml +++ b/material-intro-screen/src/main/res/values/dimens.xml @@ -1,4 +1,4 @@ - 72dp + 72dp \ No newline at end of file diff --git a/material-intro-screen/src/main/res/values/ids.xml b/material-intro-screen/src/main/res/values/ids.xml index 79ba769..dd7e099 100644 --- a/material-intro-screen/src/main/res/values/ids.xml +++ b/material-intro-screen/src/main/res/values/ids.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/material-intro-screen/src/main/res/values/strings.xml b/material-intro-screen/src/main/res/values/strings.xml index b2480a8..a18bfc0 100644 --- a/material-intro-screen/src/main/res/values/strings.xml +++ b/material-intro-screen/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - material-intro-screen - Grant permissions - Please grant needed permissions - Can\'t pass this slide. + material-intro-screen + Grant permissions + Please grant needed permissions + Can\'t pass this slide. diff --git a/material-intro-screen/src/main/res/values/styles.xml b/material-intro-screen/src/main/res/values/styles.xml index 13307cc..0addbdc 100644 --- a/material-intro-screen/src/main/res/values/styles.xml +++ b/material-intro-screen/src/main/res/values/styles.xml @@ -1,12 +1,12 @@ - + - - diff --git a/versions.gradle b/versions.gradle index 4cc1138..f533d26 100644 --- a/versions.gradle +++ b/versions.gradle @@ -1 +1,6 @@ -ext.androidSupport = "25.0.1" +ext.minSdkVersion = 16 +ext.targetSdkVersion = 26 +ext.compileSdkVersion = 26 +ext.buildToolsVersion = '26.0.2' +ext.androidSupport = "26.1.0" +