├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTE.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── agency │ │ └── tango │ │ └── materialintro │ │ ├── CustomSlide.java │ │ ├── IntroActivity.java │ │ ├── MainActivity.java │ │ └── SplashActivity.java │ └── res │ ├── drawable-hdpi │ ├── img_equipment.png │ ├── img_material_intro.png │ └── img_office.png │ ├── drawable-mdpi │ ├── img_equipment.png │ ├── img_material_intro.png │ └── img_office.png │ ├── drawable-xhdpi │ ├── img_equipment.png │ ├── img_material_intro.png │ └── img_office.png │ ├── drawable-xxhdpi │ ├── img_equipment.png │ ├── img_material_intro.png │ └── img_office.png │ ├── drawable-xxxhdpi │ ├── img_equipment.png │ ├── img_material_intro.png │ └── img_office.png │ ├── drawable │ └── splash_screen_background.xml │ ├── layout │ ├── activity_main.xml │ └── fragment_custom_slide.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── custom_slide.gif ├── facebook.png ├── finish_slide.gif ├── linkedin.png ├── permissions_slide.gif ├── simple_slide.gif └── twitter.png ├── material-intro-screen ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── agency │ │ └── tango │ │ │ └── materialintroscreen │ │ │ ├── MaterialIntroActivity.java │ │ │ ├── MessageButtonBehaviour.java │ │ │ ├── MoveUpBehaviour.java │ │ │ ├── SlideFragment.java │ │ │ ├── SlideFragmentBuilder.java │ │ │ ├── adapter │ │ │ └── SlidesAdapter.java │ │ │ ├── animations │ │ │ ├── IViewTranslation.java │ │ │ ├── ViewTranslationWrapper.java │ │ │ ├── translations │ │ │ │ ├── AlphaTranslation.java │ │ │ │ ├── DefaultAlphaTranslation.java │ │ │ │ ├── DefaultPositionTranslation.java │ │ │ │ ├── EnterDefaultTranslation.java │ │ │ │ ├── ExitDefaultTranslation.java │ │ │ │ └── NoTranslation.java │ │ │ └── wrappers │ │ │ │ ├── BackButtonTranslationWrapper.java │ │ │ │ ├── NextButtonTranslationWrapper.java │ │ │ │ ├── PageIndicatorTranslationWrapper.java │ │ │ │ ├── SkipButtonTranslationWrapper.java │ │ │ │ └── ViewPagerTranslationWrapper.java │ │ │ ├── listeners │ │ │ ├── IFinishListener.java │ │ │ ├── IPageScrolledListener.java │ │ │ ├── IPageSelectedListener.java │ │ │ ├── MessageButtonBehaviourOnPageSelected.java │ │ │ ├── ViewBehavioursOnPageChangeListener.java │ │ │ ├── clickListeners │ │ │ │ └── PermissionNotGrantedClickListener.java │ │ │ └── scrollListeners │ │ │ │ └── ParallaxScrollListener.java │ │ │ ├── parallax │ │ │ ├── ParallaxFragment.java │ │ │ ├── ParallaxFrameLayout.java │ │ │ ├── ParallaxLinearLayout.java │ │ │ ├── ParallaxRelativeLayout.java │ │ │ └── Parallaxable.java │ │ │ └── widgets │ │ │ ├── InkPageIndicator.java │ │ │ ├── OverScrollViewPager.java │ │ │ └── SwipeableViewPager.java │ └── android │ │ └── support │ │ └── v4 │ │ └── view │ │ └── CustomViewPager.java │ └── res │ ├── anim │ ├── cycle_2.xml │ ├── fade_in.xml │ ├── fade_out.xml │ └── shake_it.xml │ ├── drawable-v21 │ └── button_background.xml │ ├── drawable │ ├── button_background.xml │ ├── ic_finish.xml │ ├── ic_next.xml │ ├── ic_previous.xml │ └── ic_skip.xml │ ├── layout-land │ └── fragment_slide.xml │ ├── layout │ ├── activity_material_intro.xml │ ├── empty_fragment_slide.xml │ └── fragment_slide.xml │ ├── values-v21 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── versions.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | android: 7 | components: 8 | - tools 9 | - tools 10 | - platform-tools 11 | - build-tools-24.0.3 12 | - android-24 13 | - extra-google-google_play_services 14 | - extra-google-m2repository 15 | - extra-android-m2repository 16 | 17 | licenses: 18 | - '.+' 19 | 20 | before_cache: 21 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 22 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 23 | 24 | cache: 25 | directories: 26 | - $HOME/.m2 27 | - $HOME/.gradle 28 | 29 | before_install: 30 | - mkdir "$ANDROID_HOME/licenses" || true 31 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" 32 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 33 | 34 | notifications: 35 | slack: 36 | secure: qIOOj57yVyinyTs9SinZmp/aVN5Or/9LDg+l9SYMqVCaqM9zDk7s1/m/L7VNPdWCuWOzLf9g1+0ReBcwZ6vh+HWBQ4T1V4HQd09whhUGyW9kMj3BKE0gWpIJLYKuhM551auv3FVzTp3u27q4W0zgiXB8qHWatTQu9rcPumG+IJaZD1uHsbhrQq0RLD8n8hWjQAdkRKRtSo4UR55sTK35uGRZbMFcyJSiStBXRP43w2kTR1MxIst4r9NeOx/sjebBQ/XxabKJgqAHhue80O3Cy8s0u59NDHOMpqJOu00cdKbtmhePQsY0FUl5/689Xdc+bDs3OcwGWbokaFEjXLwA1De+CIz0NMjgdtyHIbEGWcYav8jujke4wYaAtQRPgKHtVL9EpIUX07jPznstRNV8T3H1qrf2S5xHW6elZ7nLOnYuDKsgETmEuDQLAg8ibYQTF4zNBYGFwvC3GOJCqCu+o40OwmFghyohmXxSmo8Cg019V/hOtmYThaFcyDQhN8QGkUSqHrjDNRxyyye2JHvU+bJlTshonZlPh2gM9NA9Tf/3fMEobtnA5XYurntj43UhdZ4HdsYjbrFhbOrXEBRx1mG8gcCFgexz/3E9wq7GN0fqm6LMB8radqUbP0hAd2cADlN9suCWWLVnufLAiS5iqo55M2e9u749p+e+ESCLXIo= -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG: 2 | 3 | #### 0.0.5 4 | - Reimplemented blocking swipes on slides 5 | - Reimplemented last slide alpha exit transition effect 6 | 7 | #### 0.0.4 8 | - Added support for showing snackbars 9 | - Added support for asking permissions in custom slides 10 | - Fix: canMoveFurther() method works on last slide 11 | - Fix: Library isn't crashing when no slides provided 12 | 13 | #### 0.0.3 14 | - Added onFinish method 15 | 16 | #### 0.0.2 17 | - Added API for animations 18 | - Added Travis config file 19 | - Added Splash Screen for example app 20 | - Bug fixes 21 | 22 | #### 0.0.1 23 | - Initial commit with library 24 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | Feel free to contribute code to Material Intro Screen. You can do it by forking the repository via Github and sending pull request with changes. 3 | 4 | When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. Also be sure that all tests are passing. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tango Agency 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Material Intro Screen 2 | [ ![Download](https://api.bintray.com/packages/tangoagency/maven/material-intro-screen/images/download.svg) ](https://bintray.com/tangoagency/maven/material-intro-screen/_latestVersion) 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/753f46972d8740d1984f8beb7d04fb9d)](https://www.codacy.com/app/TangoAgency/material-intro-screen?utm_source=github.com&utm_medium=referral&utm_content=TangoAgency/material-intro-screen&utm_campaign=badger) 4 | [![Build Status](https://travis-ci.org/TangoAgency/material-intro-screen.svg?branch=master)](https://travis-ci.org/TangoAgency/material-intro-screen) 5 | [![Android Arsenal Material Intro Screen](https://img.shields.io/badge/Android%20Arsenal-Material--Intro--Screen-green.svg?style=true)](http://android-arsenal.com/details/1/4368) 6 | 7 | Material intro screen is inspired by [Material Intro] and developed with love from scratch. I decided to rewrite completely almost all features in order to make Android intro screen easy to use for everyone and extensible as possible. 8 | ## Features 9 | - [Easily add new slides][Intro Activity] 10 | - [Custom slides][Custom Slide] 11 | - [Parallax slides][Parallax Slide] 12 | - Easy extensible api 13 | - Android TV support! 14 | - Material design at it's best!!! 15 | 16 | | [Simple slide][SimpleSlide] | [Custom slide][Custom Slide] | [Permission slide][PermissionSlide] | [Finish slide][FinishSlide] 17 | |:-:|:-:|:-:|:-:| 18 | | ![Simple slide] | ![Customslide] | ![Permission slide] | ![Finish slide] | 19 | 20 | ## Usage 21 | ### Step 1: 22 | #### Add gradle dependecy 23 | ``` 24 | dependencies { 25 | compile 'agency.tango.android:material-intro-screen:{latest_release}' 26 | } 27 | ``` 28 | ### Step 2: 29 | #### First, your [intro activity][Intro Activity] class needs to extend MaterialIntroActivity: 30 | ```java 31 | public class IntroActivity extends MaterialIntroActivity 32 | ``` 33 | ### Step 3: 34 | #### Add activity to [manifest][Manifest] with defined theme: 35 | ```xml 36 | 39 | ``` 40 | ### Step 4: 41 | #### [Add slides:][Intro Activity] 42 | ```java 43 | @Override 44 | protected void onCreate(@Nullable Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | 47 | addSlide(new SlideFragmentBuilder() 48 | .backgroundColor(R.color.colorPrimary) 49 | .buttonsColor(R.color.colorAccent) 50 | .possiblePermissions(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.READ_SMS}) 51 | .neededPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}) 52 | .image(agency.tango.materialintroscreen.R.drawable.ic_next) 53 | .title("title 3") 54 | .description("Description 3") 55 | .build(), 56 | new MessageButtonBehaviour(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | showMessage("We provide solutions to make you love your work"); 60 | } 61 | }, "Work with love")); 62 | } 63 | ``` 64 | #### Explanation of SlideFragment usage: 65 | - ```possiblePermissions``` ⇾ permissions which are not necessary to be granted 66 | - ```neededPersmissions``` ⇾ permission which are needed to be granted to move further from that slide 67 | - ```MessageButtonBehaviour``` ⇾ create a new instance only if you want to have a custom action or text on a message button 68 | 69 | ### Step 5: 70 | #### Customize Intro Activity: 71 | - ```setSkipButtonVisible()``` ⇾ show skip button instead of back button on the left bottom of screen 72 | - ```hideBackButton()``` ⇾ hides any button on the left bottom of screen 73 | - ```enableLastSlideAlphaExitTransition()``` ⇾ set if the last slide should disapear with alpha hiding effect 74 | 75 | #### Customizing view animations: 76 | 77 | You can set enter, default and exit translation for every view in intro activity. To achive this you need to get translation wrapper for chosen view (for example: ```getNextButtonTranslationWrapper()```) and set there new class which will implement ```IViewTranslation``` 78 | ```java 79 | getBackButtonTranslationWrapper() 80 | .setEnterTranslation(new IViewTranslation() { 81 | @Override 82 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 83 | view.setAlpha(percentage); 84 | } 85 | }); 86 | ``` 87 | #### Available [translation wrappers][TranslationWrapper]: 88 | - ```getNextButtonTranslationWrapper()``` 89 | - ```getBackButtonTranslationWrapper()``` 90 | - ```getPageIndicatorTranslationWrapper()``` 91 | - ```getViewPagerTranslationWrapper()``` 92 | - ```getSkipButtonTranslationWrapper()``` 93 | 94 | ## Custom slides 95 | #### Of course you are able to implement completely custom slides. You only need to extend SlideFragment and override following functions: 96 | - ```backgroundColor()``` 97 | - ```buttonsColor()``` 98 | - ```canMoveFurther()``` (only if you want to stop user from being able to move further before he will do some action) 99 | - ```cantMoveFurtherErrorMessage()``` (as above) 100 | 101 | #### If you want to use parallax in a fragment please use one of the below views: 102 | - [```ParallaxFrameLayout```][ParallaxFrame] 103 | - [```ParallaxLinearLayout```][ParallaxLinear] 104 | - [```ParallaxRelativeLayout```][ParallaxRelative] 105 | 106 | #### And set there the [app:layout_parallaxFactor][ParallaxFactor] attribute: 107 | ```xml 108 | 110 | 111 | 114 | ``` 115 | 116 | All features which are not available in simple Slide Fragment are shown here: [Custom Slide] 117 | 118 | ## Things I have used to create this 119 | - For parallax I have used files from [Material Intro] by [@HeinrichReimer] 120 | - [InkPageIndicator.java] by [@NickButcher] 121 | - Images used to create sample app are from [freepik] 122 | - For over scroll effect on last slide I have partially used [Android-Overscroll-ViewPager] 123 | 124 | ## Getting Help 125 | 126 | To report a specific problem or feature request, [open a new issue on Github](https://github.com/TangoAgency/material-intro-screen/issues/new). 127 | 128 | ## Company 129 | 130 | [![Facebook](https://github.com/TangoAgency/material-intro-screen/blob/master/images/facebook.png)](https://www.facebook.com/TangoDigitalAgency)     [![Twitter](https://github.com/TangoAgency/material-intro-screen/blob/master/images/twitter.png)](https://twitter.com/Tango_Agency)     [![LinkedIn](https://github.com/TangoAgency/material-intro-screen/blob/master/images/linkedin.png)](https://www.linkedin.com/company/tango-digital-agency) 131 | 132 | [Here](https://github.com/TangoAgency/) you can see open source work developed by Tango Agency. 133 | 134 | Whether you're searching for a new partner or trusted team for creating your new great product we are always ready to start work with you. 135 | 136 | You can contact us via contact@tango.agency. 137 | Thanks in advance. 138 | 139 | [Custom Slide]: 140 | [Material Intro]: 141 | [@HeinrichReimer]: 142 | [InkPageIndicator.java]: 143 | [@NickButcher]: 144 | [freepik]: 145 | [Simple slide]: 146 | [Customslide]: 147 | [Permission slide]: 148 | [Finish slide]: 149 | [Intro Activity]: 150 | [Parallax Slide]: 151 | [PermissionSlide]: 152 | [FinishSlide]: 153 | [SimpleSlide]: 154 | [ParallaxFrame]: 155 | [ParallaxLinear]: 156 | [ParallaxRelative]: 157 | [ParallaxFactor]: 158 | [Manifest]: 159 | [TranslationWrapper]: 160 | [Android-Overscroll-ViewPager]: -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply from: "$rootDir/versions.gradle" 4 | 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion "25.0.0" 8 | defaultConfig { 9 | applicationId "agency.tango.materialintro" 10 | minSdkVersion 15 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(include: ['*.jar'], dir: 'libs') 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile "com.android.support:appcompat-v7:${project.androidSupport}" 30 | testCompile 'junit:junit:4.12' 31 | compile project(path: ':material-intro-screen') 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/agency/tango/materialintro/CustomSlide.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintro; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.CheckBox; 9 | 10 | import agency.tango.materialintroscreen.SlideFragment; 11 | 12 | public class CustomSlide extends SlideFragment { 13 | private CheckBox checkBox; 14 | 15 | @Nullable 16 | @Override 17 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 18 | final View view = inflater.inflate(R.layout.fragment_custom_slide, container, false); 19 | checkBox = (CheckBox) view.findViewById(R.id.checkBox); 20 | return view; 21 | } 22 | 23 | @Override 24 | public int backgroundColor() { 25 | return R.color.custom_slide_background; 26 | } 27 | 28 | @Override 29 | public int buttonsColor() { 30 | return R.color.custom_slide_buttons; 31 | } 32 | 33 | @Override 34 | public boolean canMoveFurther() { 35 | return checkBox.isChecked(); 36 | } 37 | 38 | @Override 39 | public String cantMoveFurtherErrorMessage() { 40 | return getString(R.string.error_message); 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/agency/tango/materialintro/IntroActivity.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintro; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.support.annotation.FloatRange; 6 | import android.support.annotation.Nullable; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import agency.tango.materialintroscreen.MaterialIntroActivity; 11 | import agency.tango.materialintroscreen.MessageButtonBehaviour; 12 | import agency.tango.materialintroscreen.SlideFragmentBuilder; 13 | import agency.tango.materialintroscreen.animations.IViewTranslation; 14 | 15 | public class IntroActivity extends MaterialIntroActivity { 16 | @Override 17 | protected void onCreate(@Nullable Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | enableLastSlideAlphaExitTransition(true); 20 | 21 | getBackButtonTranslationWrapper() 22 | .setEnterTranslation(new IViewTranslation() { 23 | @Override 24 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 25 | view.setAlpha(percentage); 26 | } 27 | }); 28 | 29 | addSlide(new SlideFragmentBuilder() 30 | .backgroundColor(R.color.first_slide_background) 31 | .buttonsColor(R.color.first_slide_buttons) 32 | .image(R.drawable.img_office) 33 | .title("Organize your time with us") 34 | .description("Would you try?") 35 | .build(), 36 | new MessageButtonBehaviour(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | showMessage("We provide solutions to make you love your work"); 40 | } 41 | }, "Work with love")); 42 | 43 | addSlide(new SlideFragmentBuilder() 44 | .backgroundColor(R.color.second_slide_background) 45 | .buttonsColor(R.color.second_slide_buttons) 46 | .title("Want more?") 47 | .description("Go on") 48 | .build()); 49 | 50 | addSlide(new CustomSlide()); 51 | 52 | addSlide(new SlideFragmentBuilder() 53 | .backgroundColor(R.color.third_slide_background) 54 | .buttonsColor(R.color.third_slide_buttons) 55 | .possiblePermissions(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.READ_SMS}) 56 | .neededPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}) 57 | .image(R.drawable.img_equipment) 58 | .title("We provide best tools") 59 | .description("ever") 60 | .build(), 61 | new MessageButtonBehaviour(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | showMessage("Try us!"); 65 | } 66 | }, "Tools")); 67 | 68 | addSlide(new SlideFragmentBuilder() 69 | .backgroundColor(R.color.fourth_slide_background) 70 | .buttonsColor(R.color.fourth_slide_buttons) 71 | .title("That's it") 72 | .description("Would you join us?") 73 | .build()); 74 | } 75 | 76 | @Override 77 | public void onFinish() { 78 | super.onFinish(); 79 | Toast.makeText(this, "Try this library in your project! :)", Toast.LENGTH_SHORT).show(); 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/agency/tango/materialintro/MainActivity.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintro; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.AppCompatButton; 7 | import android.view.View; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | AppCompatButton button; 11 | 12 | @Override 13 | public void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | button = (AppCompatButton) findViewById(R.id.btn_launch_activity); 18 | button.setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | Intent intent = new Intent(MainActivity.this, IntroActivity.class); 22 | startActivity(intent); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/agency/tango/materialintro/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintro; 2 | 3 | import android.content.Intent; 4 | import android.support.v4.app.TaskStackBuilder; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | public class SplashActivity extends AppCompatActivity { 8 | @Override 9 | protected void onStart() { 10 | super.onStart(); 11 | 12 | TaskStackBuilder.create(this) 13 | .addNextIntentWithParentStack(new Intent(this, MainActivity.class)) 14 | .addNextIntent(new Intent(this, IntroActivity.class)) 15 | .startActivities(); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-hdpi/img_equipment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_material_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-hdpi/img_material_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-hdpi/img_office.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-mdpi/img_equipment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_material_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-mdpi/img_material_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-mdpi/img_office.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xhdpi/img_equipment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_material_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xhdpi/img_material_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xhdpi/img_office.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxhdpi/img_equipment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_material_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxhdpi/img_material_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxhdpi/img_office.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxxhdpi/img_equipment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_material_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxxhdpi/img_material_intro.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/drawable-xxxhdpi/img_office.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_screen_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_custom_slide.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 20 | 21 | 22 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #880E4F 6 | 7 | #E91E63 8 | #880E4F 9 | 10 | #9C27B0 11 | #4A148C 12 | 13 | #009688 14 | #004D40 15 | 16 | #795548 17 | #3E2723 18 | 19 | #3F51B5 20 | #1A237E 21 | 22 | 23 | @color/colorPrimary 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Material Intro 3 | I accept terms and conditions. 4 | Please accept terms and conditions first. 5 | Launch intro activity 6 | Lorem ipsum \n\ndolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | 20 | tasks.withType(Javadoc) { 21 | 22 | options.memberLevel = JavadocMemberLevel.PROTECTED 23 | options.links("http://docs.oracle.com/javase/7/docs/api/") 24 | options.links("http://developer.android.com/reference/") 25 | options.tags = ['sample'] 26 | exclude '**/BuildConfig.java' 27 | exclude '**/R.java' 28 | } 29 | 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | VERSION_NAME=0.0.5 21 | VERSION_CODE=5 22 | GROUP=agency.tango.android 23 | 24 | POM_DESCRIPTION=Material Intro Screen 25 | POM_URL=https://github.com/TangoAgency/material-intro-screen 26 | POM_SCM_URL=https://github.com/TangoAgency/material-intro-screen 27 | POM_SCM_CONNECTION=scm:git@github.com/TangoAgency/material-intro-screen.git 28 | POM_SCM_DEV_CONNECTION=scm:ggit@github.com/TangoAgency/material-intro-screen.git 29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 31 | POM_LICENCE_DIST=repo 32 | POM_DEVELOPER_ID=tango 33 | POM_DEVELOPER_NAME=Tango Agency -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/custom_slide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/custom_slide.gif -------------------------------------------------------------------------------- /images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/facebook.png -------------------------------------------------------------------------------- /images/finish_slide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/finish_slide.gif -------------------------------------------------------------------------------- /images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/linkedin.png -------------------------------------------------------------------------------- /images/permissions_slide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/permissions_slide.gif -------------------------------------------------------------------------------- /images/simple_slide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/simple_slide.gif -------------------------------------------------------------------------------- /images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoAgency/material-intro-screen/01dd51d1ad601463b7fd6fb2601772c12768c73b/images/twitter.png -------------------------------------------------------------------------------- /material-intro-screen/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /material-intro-screen/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | apply from: "$rootDir/versions.gradle" 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion "25.0.0" 8 | 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | debug { 20 | defaultPublishConfig "debug" 21 | } 22 | 23 | android.libraryVariants.all { variant -> 24 | project.task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { 25 | title = "Documentation for Android $project.android.defaultConfig.versionName v$project.android.defaultConfig.versionCode" 26 | description = "Generates Javadoc for $variant.name." 27 | group = 'Documentation' 28 | 29 | destinationDir = new File("${project.getProjectDir()}/javaDoc/", variant.baseName) 30 | source = variant.javaCompile.source 31 | 32 | ext.androidJar = "${project.android.sdkDirectory}/platforms/${project.android.compileSdkVersion}/android.jar" 33 | classpath = project.files(variant.javaCompile.classpath.files) + project.files(ext.androidJar) 34 | 35 | options.memberLevel = JavadocMemberLevel.PROTECTED 36 | options.links("http://docs.oracle.com/javase/7/docs/api/") 37 | options.links("http://developer.android.com/reference/") 38 | options.tags = ['sample'] 39 | exclude '**/BuildConfig.java' 40 | exclude '**/R.java' 41 | } 42 | } 43 | } 44 | 45 | lintOptions { 46 | abortOnError false 47 | } 48 | 49 | dependencies { 50 | compile fileTree(include: ['*.jar'], dir: 'libs') 51 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 52 | exclude group: 'com.android.support', module: 'support-annotations' 53 | }) 54 | compile "com.android.support:appcompat-v7:${project.androidSupport}" 55 | compile "com.android.support:design:${project.androidSupport}" 56 | } 57 | 58 | publish { 59 | userOrg = 'tangoagency' 60 | groupId = 'agency.tango.android' 61 | artifactId = 'material-intro-screen' 62 | publishVersion = '0.0.5' 63 | desc = '' 64 | website = 'https://github.com/TangoAgency/material-intro-screen' 65 | } 66 | } -------------------------------------------------------------------------------- /material-intro-screen/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=material-intro-screen 2 | POM_ARTIFACT_ID=material-intro-screen 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /material-intro-screen/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/MaterialIntroActivity.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.content.res.ColorStateList; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.annotation.ColorRes; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.design.widget.CoordinatorLayout; 11 | import android.support.design.widget.Snackbar; 12 | import android.support.v4.content.ContextCompat; 13 | import android.support.v4.view.ViewCompat; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.util.SparseArray; 16 | import android.view.KeyEvent; 17 | import android.view.View; 18 | import android.view.Window; 19 | import android.view.WindowManager; 20 | import android.widget.Button; 21 | import android.widget.ImageButton; 22 | import android.widget.LinearLayout; 23 | 24 | import agency.tango.materialintroscreen.adapter.SlidesAdapter; 25 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 26 | import agency.tango.materialintroscreen.animations.wrappers.BackButtonTranslationWrapper; 27 | import agency.tango.materialintroscreen.animations.wrappers.NextButtonTranslationWrapper; 28 | import agency.tango.materialintroscreen.animations.wrappers.PageIndicatorTranslationWrapper; 29 | import agency.tango.materialintroscreen.animations.wrappers.SkipButtonTranslationWrapper; 30 | import agency.tango.materialintroscreen.animations.wrappers.ViewPagerTranslationWrapper; 31 | import agency.tango.materialintroscreen.listeners.IFinishListener; 32 | import agency.tango.materialintroscreen.listeners.IPageScrolledListener; 33 | import agency.tango.materialintroscreen.listeners.IPageSelectedListener; 34 | import agency.tango.materialintroscreen.listeners.MessageButtonBehaviourOnPageSelected; 35 | import agency.tango.materialintroscreen.listeners.ViewBehavioursOnPageChangeListener; 36 | import agency.tango.materialintroscreen.listeners.clickListeners.PermissionNotGrantedClickListener; 37 | import agency.tango.materialintroscreen.listeners.scrollListeners.ParallaxScrollListener; 38 | import agency.tango.materialintroscreen.widgets.InkPageIndicator; 39 | import agency.tango.materialintroscreen.widgets.OverScrollViewPager; 40 | import agency.tango.materialintroscreen.widgets.SwipeableViewPager; 41 | 42 | import static android.view.View.GONE; 43 | 44 | public abstract class MaterialIntroActivity extends AppCompatActivity { 45 | private SwipeableViewPager viewPager; 46 | private InkPageIndicator pageIndicator; 47 | private SlidesAdapter adapter; 48 | private ImageButton backButton; 49 | private ImageButton skipButton; 50 | private ImageButton nextButton; 51 | private CoordinatorLayout coordinatorLayout; 52 | private Button messageButton; 53 | private LinearLayout navigationView; 54 | private OverScrollViewPager overScrollLayout; 55 | 56 | private ArgbEvaluator argbEvaluator = new ArgbEvaluator(); 57 | 58 | private ViewTranslationWrapper nextButtonTranslationWrapper; 59 | private ViewTranslationWrapper backButtonTranslationWrapper; 60 | private ViewTranslationWrapper pageIndicatorTranslationWrapper; 61 | private ViewTranslationWrapper viewPagerTranslationWrapper; 62 | private ViewTranslationWrapper skipButtonTranslationWrapper; 63 | 64 | private MessageButtonBehaviourOnPageSelected messageButtonBehaviourOnPageSelected; 65 | 66 | private View.OnClickListener permissionNotGrantedClickListener; 67 | private View.OnClickListener finishScreenClickListener; 68 | 69 | private SparseArray messageButtonBehaviours = new SparseArray<>(); 70 | 71 | @Override 72 | protected void onCreate(@Nullable Bundle savedInstanceState) { 73 | super.onCreate(savedInstanceState); 74 | 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 76 | Window window = getWindow(); 77 | window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 78 | } 79 | 80 | setContentView(R.layout.activity_material_intro); 81 | 82 | overScrollLayout = (OverScrollViewPager) findViewById(R.id.view_pager_slides); 83 | viewPager = overScrollLayout.getOverScrollView(); 84 | pageIndicator = (InkPageIndicator) findViewById(R.id.indicator); 85 | backButton = (ImageButton) findViewById(R.id.button_back); 86 | nextButton = (ImageButton) findViewById(R.id.button_next); 87 | skipButton = (ImageButton) findViewById(R.id.button_skip); 88 | messageButton = (Button) findViewById(R.id.button_message); 89 | coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout_slide); 90 | navigationView = (LinearLayout) findViewById(R.id.navigation_view); 91 | 92 | adapter = new SlidesAdapter(getSupportFragmentManager()); 93 | 94 | viewPager.setAdapter(adapter); 95 | viewPager.setOffscreenPageLimit(2); 96 | pageIndicator.setViewPager(viewPager); 97 | 98 | nextButtonTranslationWrapper = new NextButtonTranslationWrapper(nextButton); 99 | initOnPageChangeListeners(); 100 | 101 | permissionNotGrantedClickListener = new PermissionNotGrantedClickListener(this, nextButtonTranslationWrapper); 102 | finishScreenClickListener = new FinishScreenClickListener(); 103 | 104 | setBackButtonVisible(); 105 | 106 | viewPager.post(new Runnable() { 107 | @Override 108 | public void run() { 109 | if (adapter.getCount() == 0) { 110 | finish(); 111 | } else { 112 | int currentItem = viewPager.getCurrentItem(); 113 | messageButtonBehaviourOnPageSelected.pageSelected(currentItem); 114 | nextButtonBehaviour(currentItem, adapter.getItem(currentItem)); 115 | } 116 | } 117 | }); 118 | } 119 | 120 | @Override 121 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 122 | SlideFragment fragment = adapter.getItem(viewPager.getCurrentItem()); 123 | boolean hasPermissionToGrant = fragment.hasNeededPermissionsToGrant(); 124 | if (!hasPermissionToGrant) { 125 | viewPager.setSwipingRightAllowed(true); 126 | nextButtonBehaviour(viewPager.getCurrentItem(), fragment); 127 | messageButtonBehaviourOnPageSelected.pageSelected(viewPager.getCurrentItem()); 128 | } else { 129 | showPermissionsNotGrantedError(); 130 | } 131 | 132 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 133 | } 134 | 135 | @Override 136 | public void onBackPressed() { 137 | moveBack(); 138 | } 139 | 140 | @Override 141 | public boolean onKeyDown(int keyCode, KeyEvent event) { 142 | switch (keyCode) { 143 | case KeyEvent.KEYCODE_DPAD_CENTER: 144 | if (messageButtonBehaviours.get(viewPager.getCurrentItem()) != null) { 145 | messageButton.performClick(); 146 | } 147 | break; 148 | case KeyEvent.KEYCODE_DPAD_RIGHT: 149 | int position = viewPager.getCurrentItem(); 150 | if (adapter.isLastSlide(position) && adapter.getItem(position).canMoveFurther()) { 151 | performFinish(); 152 | } else if (adapter.shouldLockSlide(position)) { 153 | errorOccurred(adapter.getItem(position)); 154 | } else { 155 | viewPager.moveToNextPage(); 156 | } 157 | break; 158 | case KeyEvent.KEYCODE_DPAD_LEFT: 159 | moveBack(); 160 | break; 161 | default: 162 | return super.onKeyDown(keyCode, event); 163 | } 164 | return super.onKeyDown(keyCode, event); 165 | } 166 | 167 | public void showPermissionsNotGrantedError() { 168 | showError(getString(R.string.please_grant_permissions)); 169 | } 170 | 171 | /** 172 | * Add SlideFragment to IntroScreen 173 | * 174 | * @param slideFragment Fragment to add 175 | */ 176 | @SuppressWarnings("unused") 177 | public void addSlide(SlideFragment slideFragment) { 178 | adapter.addItem(slideFragment); 179 | } 180 | 181 | /** 182 | * Add SlideFragment to IntroScreen 183 | * 184 | * @param slideFragment Fragment to add 185 | * @param messageButtonBehaviour Add behaviour for message button 186 | */ 187 | @SuppressWarnings("unused") 188 | public void addSlide(SlideFragment slideFragment, MessageButtonBehaviour messageButtonBehaviour) { 189 | adapter.addItem(slideFragment); 190 | messageButtonBehaviours.put(adapter.getLastItemPosition(), messageButtonBehaviour); 191 | } 192 | 193 | /** 194 | * Set skip button instead of back button 195 | */ 196 | @SuppressWarnings("unused") 197 | public void setSkipButtonVisible() { 198 | backButton.setVisibility(GONE); 199 | 200 | skipButton.setVisibility(View.VISIBLE); 201 | skipButton.setOnClickListener(new View.OnClickListener() { 202 | @Override 203 | public void onClick(View v) { 204 | for (int position = viewPager.getCurrentItem(); position < adapter.getCount(); position++) { 205 | if (!adapter.getItem(position).canMoveFurther()) { 206 | viewPager.setCurrentItem(position, true); 207 | showError(adapter.getItem(position).cantMoveFurtherErrorMessage()); 208 | return; 209 | } 210 | } 211 | viewPager.setCurrentItem(adapter.getLastItemPosition(), true); 212 | } 213 | }); 214 | } 215 | 216 | /** 217 | * Set back button visible 218 | */ 219 | public void setBackButtonVisible() { 220 | skipButton.setVisibility(GONE); 221 | 222 | backButton.setVisibility(View.VISIBLE); 223 | backButton.setOnClickListener(new View.OnClickListener() { 224 | @Override 225 | public void onClick(View v) { 226 | viewPager.setCurrentItem(viewPager.getPreviousItem(), true); 227 | } 228 | }); 229 | } 230 | 231 | /** 232 | * Hides any back button 233 | */ 234 | @SuppressWarnings("unused") 235 | public void hideBackButton() { 236 | backButton.setVisibility(View.INVISIBLE); 237 | skipButton.setVisibility(View.GONE); 238 | } 239 | 240 | /** 241 | * Get translation wrapper for next button 242 | * 243 | * @return ViewTranslationWrapper 244 | */ 245 | public ViewTranslationWrapper getNextButtonTranslationWrapper() { 246 | return nextButtonTranslationWrapper; 247 | } 248 | 249 | /** 250 | * Get translation wrapper for back button 251 | * 252 | * @return ViewTranslationWrapper 253 | */ 254 | @SuppressWarnings("unused") 255 | public ViewTranslationWrapper getBackButtonTranslationWrapper() { 256 | return backButtonTranslationWrapper; 257 | } 258 | 259 | /** 260 | * Get translation wrapper for page indicator 261 | * 262 | * @return ViewTranslationWrapper 263 | */ 264 | @SuppressWarnings("unused") 265 | public ViewTranslationWrapper getPageIndicatorTranslationWrapper() { 266 | return pageIndicatorTranslationWrapper; 267 | } 268 | 269 | /** 270 | * Get translation wrapper for view pager 271 | * 272 | * @return ViewTranslationWrapper 273 | */ 274 | @SuppressWarnings("unused") 275 | public ViewTranslationWrapper getViewPagerTranslationWrapper() { 276 | return viewPagerTranslationWrapper; 277 | } 278 | 279 | /** 280 | * Get translation wrapper for skip button 281 | * 282 | * @return ViewTranslationWrapper 283 | */ 284 | @SuppressWarnings("unused") 285 | public ViewTranslationWrapper getSkipButtonTranslationWrapper() { 286 | return skipButtonTranslationWrapper; 287 | } 288 | 289 | /** 290 | * Set if last screen should be able to exit with alpha transition 291 | * 292 | * @param enableAlphaExitTransition should enable alpha exit transition 293 | */ 294 | @SuppressWarnings("unused") 295 | public void enableLastSlideAlphaExitTransition(boolean enableAlphaExitTransition) { 296 | viewPager.alphaExitTransitionEnabled(enableAlphaExitTransition); 297 | } 298 | 299 | /** 300 | * Show snackbar message 301 | * 302 | * @param message Message which will be visible to user 303 | */ 304 | public void showMessage(String message) { 305 | showError(message); 306 | } 307 | 308 | /** 309 | * Override to execute this method on finish intro activity 310 | */ 311 | public void onFinish() { 312 | } 313 | 314 | private void initOnPageChangeListeners() { 315 | messageButtonBehaviourOnPageSelected = new MessageButtonBehaviourOnPageSelected(messageButton, adapter, messageButtonBehaviours); 316 | 317 | backButtonTranslationWrapper = new BackButtonTranslationWrapper(backButton); 318 | pageIndicatorTranslationWrapper = new PageIndicatorTranslationWrapper(pageIndicator); 319 | viewPagerTranslationWrapper = new ViewPagerTranslationWrapper(viewPager); 320 | skipButtonTranslationWrapper = new SkipButtonTranslationWrapper(skipButton); 321 | 322 | overScrollLayout.registerFinishListener(new IFinishListener() { 323 | @Override 324 | public void doOnFinish() { 325 | performFinish(); 326 | } 327 | }); 328 | 329 | viewPager.addOnPageChangeListener(new ViewBehavioursOnPageChangeListener(adapter) 330 | .registerViewTranslationWrapper(nextButtonTranslationWrapper) 331 | .registerViewTranslationWrapper(backButtonTranslationWrapper) 332 | .registerViewTranslationWrapper(pageIndicatorTranslationWrapper) 333 | .registerViewTranslationWrapper(viewPagerTranslationWrapper) 334 | .registerViewTranslationWrapper(skipButtonTranslationWrapper) 335 | 336 | .registerOnPageScrolled(new IPageScrolledListener() { 337 | @Override 338 | public void pageScrolled(final int position, float offset) { 339 | viewPager.post(new Runnable() { 340 | @Override 341 | public void run() { 342 | if (adapter.getItem(position).hasNeededPermissionsToGrant() || !adapter.getItem(position).canMoveFurther()) { 343 | viewPager.setCurrentItem(position, true); 344 | pageIndicator.clearJoiningFractions(); 345 | } 346 | } 347 | }); 348 | } 349 | }) 350 | .registerOnPageScrolled(new ColorTransitionScrollListener()) 351 | .registerOnPageScrolled(new ParallaxScrollListener(adapter)) 352 | 353 | .registerPageSelectedListener(messageButtonBehaviourOnPageSelected) 354 | .registerPageSelectedListener(new IPageSelectedListener() { 355 | @Override 356 | public void pageSelected(int position) { 357 | nextButtonBehaviour(position, adapter.getItem(position)); 358 | 359 | if (adapter.shouldFinish(position)) { 360 | performFinish(); 361 | } 362 | } 363 | })); 364 | } 365 | 366 | @SuppressWarnings("PointlessBooleanExpression") 367 | private void nextButtonBehaviour(final int position, final SlideFragment fragment) { 368 | boolean hasPermissionToGrant = fragment.hasNeededPermissionsToGrant(); 369 | if (hasPermissionToGrant) { 370 | nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_next)); 371 | nextButton.setOnClickListener(permissionNotGrantedClickListener); 372 | } else if (adapter.isLastSlide(position)) { 373 | nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_finish)); 374 | nextButton.setOnClickListener(finishScreenClickListener); 375 | } else { 376 | nextButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_next)); 377 | nextButton.setOnClickListener(new View.OnClickListener() { 378 | @Override 379 | public void onClick(View v) { 380 | if (fragment.canMoveFurther() == false) { 381 | errorOccurred(fragment); 382 | } else { 383 | viewPager.moveToNextPage(); 384 | } 385 | } 386 | }); 387 | } 388 | } 389 | 390 | private void performFinish() { 391 | onFinish(); 392 | finish(); 393 | } 394 | 395 | private void moveBack() { 396 | if (viewPager.getCurrentItem() == 0) { 397 | finish(); 398 | } else { 399 | viewPager.setCurrentItem(viewPager.getPreviousItem(), true); 400 | } 401 | } 402 | 403 | private void errorOccurred(SlideFragment slideFragment) { 404 | nextButtonTranslationWrapper.error(); 405 | showError(slideFragment.cantMoveFurtherErrorMessage()); 406 | } 407 | 408 | private void showError(String error) { 409 | Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_SHORT).setCallback(new Snackbar.Callback() { 410 | @Override 411 | public void onDismissed(Snackbar snackbar, int event) { 412 | navigationView.setTranslationY(0f); 413 | super.onDismissed(snackbar, event); 414 | } 415 | }).show(); 416 | } 417 | 418 | private Integer getBackgroundColor(int position, float positionOffset) { 419 | return (Integer) argbEvaluator.evaluate(positionOffset, color(adapter.getItem(position).backgroundColor()), color(adapter.getItem(position + 1).backgroundColor())); 420 | } 421 | 422 | private Integer getButtonsColor(int position, float positionOffset) { 423 | return (Integer) argbEvaluator.evaluate(positionOffset, color(adapter.getItem(position).buttonsColor()), color(adapter.getItem(position + 1).buttonsColor())); 424 | } 425 | 426 | private int color(@ColorRes int color) { 427 | return ContextCompat.getColor(this, color); 428 | } 429 | 430 | private class ColorTransitionScrollListener implements IPageScrolledListener { 431 | @Override 432 | public void pageScrolled(int position, float offset) { 433 | if (position < adapter.getCount() - 1) { 434 | setViewsColor(position, offset); 435 | } else if (adapter.getCount() == 1) { 436 | viewPager.setBackgroundColor(adapter.getItem(position).backgroundColor()); 437 | messageButton.setTextColor(adapter.getItem(position).backgroundColor()); 438 | 439 | tintButtons(ColorStateList.valueOf(adapter.getItem(position).buttonsColor())); 440 | } 441 | } 442 | 443 | private void setViewsColor(int position, float offset) { 444 | int backgroundColor = getBackgroundColor(position, offset); 445 | viewPager.setBackgroundColor(backgroundColor); 446 | messageButton.setTextColor(backgroundColor); 447 | 448 | int buttonsColor = getButtonsColor(position, offset); 449 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 450 | getWindow().setStatusBarColor(buttonsColor); 451 | } 452 | pageIndicator.setPageIndicatorColor(buttonsColor); 453 | 454 | tintButtons(ColorStateList.valueOf(buttonsColor)); 455 | } 456 | 457 | private void tintButtons(ColorStateList color) { 458 | ViewCompat.setBackgroundTintList(nextButton, color); 459 | ViewCompat.setBackgroundTintList(backButton, color); 460 | ViewCompat.setBackgroundTintList(skipButton, color); 461 | } 462 | } 463 | 464 | private class FinishScreenClickListener implements View.OnClickListener { 465 | @Override 466 | public void onClick(View v) { 467 | SlideFragment slideFragment = adapter.getItem(adapter.getLastItemPosition()); 468 | if (!slideFragment.canMoveFurther()) { 469 | errorOccurred(slideFragment); 470 | } else { 471 | performFinish(); 472 | } 473 | } 474 | } 475 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/MessageButtonBehaviour.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen; 2 | 3 | import android.view.View; 4 | 5 | @SuppressWarnings("unused") 6 | public class MessageButtonBehaviour { 7 | private View.OnClickListener clickListener; 8 | private String messageButtonText; 9 | 10 | public MessageButtonBehaviour(View.OnClickListener clickListener, String messageButtonText) { 11 | this.clickListener = clickListener; 12 | this.messageButtonText = messageButtonText; 13 | } 14 | 15 | public MessageButtonBehaviour(String messageButtonText) { 16 | this.messageButtonText = messageButtonText; 17 | } 18 | 19 | public View.OnClickListener getClickListener() { 20 | return clickListener; 21 | } 22 | 23 | public String getMessageButtonText() { 24 | return messageButtonText; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/MoveUpBehaviour.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.support.design.widget.Snackbar; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.LinearLayout; 9 | 10 | @SuppressWarnings("unused") 11 | public class MoveUpBehaviour extends CoordinatorLayout.Behavior { 12 | public MoveUpBehaviour(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | @Override 17 | public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) { 18 | return dependency instanceof Snackbar.SnackbarLayout; 19 | } 20 | 21 | @Override 22 | public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { 23 | float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight()); 24 | child.setTranslationY(translationY); 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragment.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen; 2 | 3 | import android.content.pm.PackageManager; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.ActivityCompat; 7 | import android.support.v4.content.ContextCompat; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import agency.tango.materialintroscreen.parallax.ParallaxFragment; 19 | 20 | public class SlideFragment extends ParallaxFragment { 21 | private final static String BACKGROUND_COLOR = "background_color"; 22 | private static final String BUTTONS_COLOR = "buttons_color"; 23 | private static final String TITLE = "title"; 24 | private static final String DESCRIPTION = "description"; 25 | private static final String NEEDED_PERMISSIONS = "needed_permission"; 26 | private static final String POSSIBLE_PERMISSIONS = "possible_permission"; 27 | private static final String IMAGE = "image"; 28 | private static final int PERMISSIONS_REQUEST_CODE = 15621; 29 | 30 | private int backgroundColor; 31 | private int buttonsColor; 32 | private int image; 33 | private String title; 34 | private String description; 35 | private String[] neededPermissions; 36 | private String[] possiblePermissions; 37 | 38 | private TextView titleTextView; 39 | private TextView descriptionTextView; 40 | private ImageView imageView; 41 | 42 | public static SlideFragment createInstance(SlideFragmentBuilder builder) { 43 | SlideFragment slideFragment = new SlideFragment(); 44 | 45 | Bundle bundle = new Bundle(); 46 | bundle.putInt(BACKGROUND_COLOR, builder.backgroundColor); 47 | bundle.putInt(BUTTONS_COLOR, builder.buttonsColor); 48 | bundle.putInt(IMAGE, builder.image); 49 | bundle.putString(TITLE, builder.title); 50 | bundle.putString(DESCRIPTION, builder.description); 51 | bundle.putStringArray(NEEDED_PERMISSIONS, builder.neededPermissions); 52 | bundle.putStringArray(POSSIBLE_PERMISSIONS, builder.possiblePermissions); 53 | 54 | slideFragment.setArguments(bundle); 55 | return slideFragment; 56 | } 57 | 58 | public static boolean isNotNullOrEmpty(String string) { 59 | return string != null && !string.isEmpty(); 60 | } 61 | 62 | @Nullable 63 | @Override 64 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 65 | View view = inflater.inflate(R.layout.fragment_slide, container, false); 66 | titleTextView = (TextView) view.findViewById(R.id.txt_title_slide); 67 | descriptionTextView = (TextView) view.findViewById(R.id.txt_description_slide); 68 | imageView = (ImageView) view.findViewById(R.id.image_slide); 69 | initializeView(); 70 | return view; 71 | } 72 | 73 | public void initializeView() { 74 | Bundle bundle = getArguments(); 75 | backgroundColor = bundle.getInt(BACKGROUND_COLOR); 76 | buttonsColor = bundle.getInt(BUTTONS_COLOR); 77 | image = bundle.getInt(IMAGE, 0); 78 | title = bundle.getString(TITLE); 79 | description = bundle.getString(DESCRIPTION); 80 | neededPermissions = bundle.getStringArray(NEEDED_PERMISSIONS); 81 | possiblePermissions = bundle.getStringArray(POSSIBLE_PERMISSIONS); 82 | 83 | updateViewWithValues(); 84 | } 85 | 86 | public int backgroundColor() { 87 | return backgroundColor; 88 | } 89 | 90 | public int buttonsColor() { 91 | return buttonsColor; 92 | } 93 | 94 | public boolean hasAnyPermissionsToGrant() { 95 | boolean hasPermissionToGrant = hasPermissionsToGrant(neededPermissions); 96 | if (!hasPermissionToGrant) { 97 | hasPermissionToGrant = hasPermissionsToGrant(possiblePermissions); 98 | } 99 | return hasPermissionToGrant; 100 | } 101 | 102 | public boolean hasNeededPermissionsToGrant() { 103 | return hasPermissionsToGrant(neededPermissions); 104 | } 105 | 106 | public boolean canMoveFurther() { 107 | return true; 108 | } 109 | 110 | public String cantMoveFurtherErrorMessage() { 111 | return getString(R.string.impassable_slide); 112 | } 113 | 114 | private void updateViewWithValues() { 115 | titleTextView.setText(title); 116 | descriptionTextView.setText(description); 117 | 118 | if (image != 0) { 119 | imageView.setImageDrawable(ContextCompat.getDrawable(getActivity(), image)); 120 | imageView.setVisibility(View.VISIBLE); 121 | } 122 | } 123 | 124 | public void askForPermissions() { 125 | ArrayList notGrantedPermissions = new ArrayList<>(); 126 | 127 | if (neededPermissions != null) { 128 | for (String permission : neededPermissions) { 129 | if (isNotNullOrEmpty(permission)) { 130 | if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { 131 | notGrantedPermissions.add(permission); 132 | } 133 | } 134 | } 135 | } 136 | if (possiblePermissions != null) { 137 | for (String permission : possiblePermissions) { 138 | if (isNotNullOrEmpty(permission)) { 139 | if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { 140 | notGrantedPermissions.add(permission); 141 | } 142 | } 143 | } 144 | } 145 | 146 | String[] permissionsToGrant = removeEmptyAndNullStrings(notGrantedPermissions); 147 | ActivityCompat.requestPermissions(getActivity(), permissionsToGrant, PERMISSIONS_REQUEST_CODE); 148 | } 149 | 150 | private boolean hasPermissionsToGrant(String[] permissions) { 151 | if (permissions != null) { 152 | for (String permission : permissions) { 153 | if (isNotNullOrEmpty(permission)) { 154 | if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) { 155 | return true; 156 | } 157 | } 158 | } 159 | } 160 | return false; 161 | } 162 | 163 | @SuppressWarnings("SuspiciousMethodCalls") 164 | private String[] removeEmptyAndNullStrings(final ArrayList permissions) { 165 | List list = new ArrayList<>(permissions); 166 | list.removeAll(Collections.singleton(null)); 167 | return list.toArray(new String[list.size()]); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/SlideFragmentBuilder.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen; 2 | 3 | import android.support.annotation.ColorRes; 4 | import android.support.annotation.DrawableRes; 5 | 6 | @SuppressWarnings({"unused", "WeakerAccess"}) 7 | public class SlideFragmentBuilder { 8 | int backgroundColor; 9 | int buttonsColor; 10 | String title; 11 | String description; 12 | String[] neededPermissions; 13 | String[] possiblePermissions; 14 | int image; 15 | 16 | public SlideFragmentBuilder backgroundColor(@ColorRes int backgroundColor) { 17 | this.backgroundColor = backgroundColor; 18 | return this; 19 | } 20 | 21 | public SlideFragmentBuilder buttonsColor(@ColorRes int buttonsColor) { 22 | this.buttonsColor = buttonsColor; 23 | return this; 24 | } 25 | 26 | public SlideFragmentBuilder title(String title) { 27 | this.title = title; 28 | return this; 29 | } 30 | 31 | public SlideFragmentBuilder description(String description) { 32 | this.description = description; 33 | return this; 34 | } 35 | 36 | public SlideFragmentBuilder neededPermissions(String[] neededPermissions) { 37 | this.neededPermissions = neededPermissions; 38 | return this; 39 | } 40 | 41 | public SlideFragmentBuilder possiblePermissions(String[] possiblePermissions) { 42 | this.possiblePermissions = possiblePermissions; 43 | return this; 44 | } 45 | 46 | public SlideFragmentBuilder image(@DrawableRes int image) { 47 | this.image = image; 48 | return this; 49 | } 50 | 51 | public SlideFragment build() { 52 | return SlideFragment.createInstance(this); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/adapter/SlidesAdapter.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.adapter; 2 | 3 | import android.support.v4.app.FragmentManager; 4 | import android.support.v4.app.FragmentStatePagerAdapter; 5 | import android.view.ViewGroup; 6 | 7 | import java.util.ArrayList; 8 | 9 | import agency.tango.materialintroscreen.SlideFragment; 10 | 11 | public class SlidesAdapter extends FragmentStatePagerAdapter { 12 | private ArrayList fragments = new ArrayList<>(); 13 | 14 | public SlidesAdapter(FragmentManager fragmentManager) { 15 | super(fragmentManager); 16 | } 17 | 18 | @Override 19 | public SlideFragment getItem(int position) { 20 | return fragments.get(position); 21 | } 22 | 23 | @Override 24 | public Object instantiateItem(ViewGroup container, int position) { 25 | SlideFragment fragment = (SlideFragment) super.instantiateItem(container, position); 26 | fragments.set(position, fragment); 27 | return fragment; 28 | } 29 | 30 | @Override 31 | public int getCount() { 32 | return fragments.size(); 33 | } 34 | 35 | public void addItem(SlideFragment fragment) { 36 | fragments.add(getCount(), fragment); 37 | notifyDataSetChanged(); 38 | } 39 | 40 | public int getLastItemPosition() { 41 | return getCount() - 1; 42 | } 43 | 44 | public boolean isLastSlide(int position) { 45 | return position == getCount() - 1; 46 | } 47 | 48 | public boolean shouldFinish(int position) { 49 | return position == getCount() && getItem(getCount() - 1).canMoveFurther(); 50 | } 51 | 52 | public boolean shouldLockSlide(int position) { 53 | SlideFragment fragment = getItem(position); 54 | return !fragment.canMoveFurther() || fragment.hasNeededPermissionsToGrant(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/IViewTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | public interface IViewTranslation { 7 | void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage); 8 | } 9 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/ViewTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations; 2 | 3 | import android.support.annotation.AnimRes; 4 | import android.view.View; 5 | import android.view.animation.Animation; 6 | import android.view.animation.AnimationUtils; 7 | 8 | import agency.tango.materialintroscreen.animations.translations.NoTranslation; 9 | 10 | @SuppressWarnings("WeakerAccess") 11 | public class ViewTranslationWrapper { 12 | private View view; 13 | 14 | private IViewTranslation enterTranslation; 15 | private IViewTranslation exitTranslation; 16 | private IViewTranslation defaultTranslation; 17 | private Animation errorAnimation; 18 | 19 | public ViewTranslationWrapper(View view) { 20 | this.view = view; 21 | 22 | enterTranslation = new NoTranslation(); 23 | exitTranslation = new NoTranslation(); 24 | setErrorAnimation(0); 25 | } 26 | 27 | /** 28 | * Set translation after passing first slide 29 | * 30 | * @param enterTranslation new translation 31 | * @return ViewTranslationWrapper object 32 | */ 33 | public ViewTranslationWrapper setEnterTranslation(IViewTranslation enterTranslation) { 34 | this.enterTranslation = enterTranslation; 35 | return this; 36 | } 37 | 38 | /** 39 | * Set translation after passing last slide 40 | * 41 | * @param exitTranslation new translation 42 | * @return ViewTranslationWrapper object 43 | */ 44 | public ViewTranslationWrapper setExitTranslation(IViewTranslation exitTranslation) { 45 | this.exitTranslation = exitTranslation; 46 | return this; 47 | } 48 | 49 | /** 50 | * Set default translation 51 | * 52 | * @param defaultTranslation new translation 53 | * @return ViewTranslationWrapper object 54 | */ 55 | public ViewTranslationWrapper setDefaultTranslation(IViewTranslation defaultTranslation) { 56 | this.defaultTranslation = defaultTranslation; 57 | return this; 58 | } 59 | 60 | /** 61 | * Set view on error animation 62 | * 63 | * @param errorAnimation new animation 64 | * @return ViewTranslationWrapper object 65 | */ 66 | public ViewTranslationWrapper setErrorAnimation(@AnimRes int errorAnimation) { 67 | if (errorAnimation != 0) { 68 | this.errorAnimation = AnimationUtils.loadAnimation(view.getContext(), errorAnimation); 69 | } 70 | return this; 71 | } 72 | 73 | public void enterTranslate(float percentage) { 74 | enterTranslation.translate(view, percentage); 75 | } 76 | 77 | public void exitTranslate(float percentage) { 78 | exitTranslation.translate(view, percentage); 79 | } 80 | 81 | public void defaultTranslate(float percentage) { 82 | defaultTranslation.translate(view, percentage); 83 | } 84 | 85 | public void error() { 86 | if (errorAnimation != null) { 87 | view.startAnimation(errorAnimation); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/AlphaTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.animations.IViewTranslation; 7 | 8 | public class AlphaTranslation implements IViewTranslation { 9 | @Override 10 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 11 | view.setAlpha(1.0f - percentage); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/DefaultAlphaTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.animations.IViewTranslation; 7 | 8 | public class DefaultAlphaTranslation implements IViewTranslation { 9 | @Override 10 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 11 | view.setAlpha(1f); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/DefaultPositionTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.animations.IViewTranslation; 7 | 8 | public class DefaultPositionTranslation implements IViewTranslation { 9 | @Override 10 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 11 | view.setTranslationY(0); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/EnterDefaultTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.R; 7 | import agency.tango.materialintroscreen.animations.IViewTranslation; 8 | 9 | public class EnterDefaultTranslation implements IViewTranslation { 10 | @Override 11 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 12 | view.setTranslationY((1f - percentage) * view.getResources().getDimensionPixelOffset(R.dimen.y_offset)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/ExitDefaultTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.R; 7 | import agency.tango.materialintroscreen.animations.IViewTranslation; 8 | 9 | public class ExitDefaultTranslation implements IViewTranslation { 10 | @Override 11 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 12 | view.setTranslationY(percentage * view.getResources().getDimensionPixelOffset(R.dimen.y_offset)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/translations/NoTranslation.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.translations; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | import agency.tango.materialintroscreen.animations.IViewTranslation; 7 | 8 | public class NoTranslation implements IViewTranslation { 9 | @Override 10 | public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) { 11 | } 12 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/BackButtonTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.wrappers; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 6 | import agency.tango.materialintroscreen.animations.translations.DefaultPositionTranslation; 7 | import agency.tango.materialintroscreen.animations.translations.EnterDefaultTranslation; 8 | import agency.tango.materialintroscreen.animations.translations.ExitDefaultTranslation; 9 | 10 | public class BackButtonTranslationWrapper extends ViewTranslationWrapper { 11 | public BackButtonTranslationWrapper(View view) { 12 | super(view); 13 | 14 | setEnterTranslation(new EnterDefaultTranslation()) 15 | .setDefaultTranslation(new DefaultPositionTranslation()) 16 | .setExitTranslation(new ExitDefaultTranslation()); 17 | } 18 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/NextButtonTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.wrappers; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.R; 6 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 7 | import agency.tango.materialintroscreen.animations.translations.DefaultPositionTranslation; 8 | import agency.tango.materialintroscreen.animations.translations.ExitDefaultTranslation; 9 | 10 | public class NextButtonTranslationWrapper extends ViewTranslationWrapper { 11 | public NextButtonTranslationWrapper(View view) { 12 | super(view); 13 | 14 | setExitTranslation(new ExitDefaultTranslation()) 15 | .setDefaultTranslation(new DefaultPositionTranslation()) 16 | .setErrorAnimation(R.anim.shake_it); 17 | } 18 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/PageIndicatorTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.wrappers; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 6 | import agency.tango.materialintroscreen.animations.translations.DefaultPositionTranslation; 7 | import agency.tango.materialintroscreen.animations.translations.ExitDefaultTranslation; 8 | 9 | public class PageIndicatorTranslationWrapper extends ViewTranslationWrapper { 10 | public PageIndicatorTranslationWrapper(View view) { 11 | super(view); 12 | 13 | setDefaultTranslation(new DefaultPositionTranslation()) 14 | .setExitTranslation(new ExitDefaultTranslation()); 15 | } 16 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/SkipButtonTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.wrappers; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 6 | import agency.tango.materialintroscreen.animations.translations.DefaultPositionTranslation; 7 | import agency.tango.materialintroscreen.animations.translations.EnterDefaultTranslation; 8 | import agency.tango.materialintroscreen.animations.translations.ExitDefaultTranslation; 9 | 10 | public class SkipButtonTranslationWrapper extends ViewTranslationWrapper { 11 | public SkipButtonTranslationWrapper(View view) { 12 | super(view); 13 | 14 | setEnterTranslation(new EnterDefaultTranslation()) 15 | .setDefaultTranslation(new DefaultPositionTranslation()) 16 | .setExitTranslation(new ExitDefaultTranslation()); 17 | } 18 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/animations/wrappers/ViewPagerTranslationWrapper.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.animations.wrappers; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 6 | import agency.tango.materialintroscreen.animations.translations.AlphaTranslation; 7 | import agency.tango.materialintroscreen.animations.translations.DefaultAlphaTranslation; 8 | 9 | public class ViewPagerTranslationWrapper extends ViewTranslationWrapper { 10 | public ViewPagerTranslationWrapper(View view) { 11 | super(view); 12 | 13 | setDefaultTranslation(new DefaultAlphaTranslation()) 14 | .setExitTranslation(new AlphaTranslation()); 15 | } 16 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IFinishListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners; 2 | 3 | public interface IFinishListener { 4 | void doOnFinish(); 5 | } 6 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IPageScrolledListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners; 2 | 3 | public interface IPageScrolledListener { 4 | void pageScrolled(int position, float offset); 5 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/IPageSelectedListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners; 2 | 3 | public interface IPageSelectedListener { 4 | void pageSelected(int position); 5 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/MessageButtonBehaviourOnPageSelected.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners; 2 | 3 | import android.util.SparseArray; 4 | import android.view.View; 5 | import android.view.animation.AnimationUtils; 6 | import android.widget.Button; 7 | 8 | import agency.tango.materialintroscreen.MessageButtonBehaviour; 9 | import agency.tango.materialintroscreen.R; 10 | import agency.tango.materialintroscreen.SlideFragment; 11 | import agency.tango.materialintroscreen.adapter.SlidesAdapter; 12 | 13 | import static agency.tango.materialintroscreen.SlideFragment.isNotNullOrEmpty; 14 | 15 | public class MessageButtonBehaviourOnPageSelected implements IPageSelectedListener { 16 | private Button messageButton; 17 | private SlidesAdapter adapter; 18 | private SparseArray messageButtonBehaviours; 19 | 20 | public MessageButtonBehaviourOnPageSelected(Button messageButton, SlidesAdapter adapter, SparseArray messageButtonBehaviours) { 21 | this.messageButton = messageButton; 22 | this.adapter = adapter; 23 | this.messageButtonBehaviours = messageButtonBehaviours; 24 | } 25 | 26 | @Override 27 | public void pageSelected(int position) { 28 | final SlideFragment slideFragment = adapter.getItem(position); 29 | 30 | if (slideFragment.hasAnyPermissionsToGrant()) { 31 | showMessageButton(slideFragment); 32 | messageButton.setText(slideFragment.getActivity().getString(R.string.grant_permissions)); 33 | messageButton.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View view) { 36 | slideFragment.askForPermissions(); 37 | } 38 | }); 39 | } else if (checkIfMessageButtonHasBehaviour(position)) { 40 | showMessageButton(slideFragment); 41 | messageButton.setText(messageButtonBehaviours.get(position).getMessageButtonText()); 42 | messageButton.setOnClickListener(messageButtonBehaviours.get(position).getClickListener()); 43 | } else if (messageButton.getVisibility() != View.INVISIBLE) { 44 | messageButton.startAnimation(AnimationUtils.loadAnimation(slideFragment.getContext(), R.anim.fade_out)); 45 | messageButton.setVisibility(View.INVISIBLE); 46 | } 47 | } 48 | 49 | private boolean checkIfMessageButtonHasBehaviour(int position) { 50 | return messageButtonBehaviours.get(position) != null && isNotNullOrEmpty(messageButtonBehaviours.get(position).getMessageButtonText()); 51 | } 52 | 53 | private void showMessageButton(final SlideFragment fragment) { 54 | if (messageButton.getVisibility() != View.VISIBLE) { 55 | messageButton.setVisibility(View.VISIBLE); 56 | if (fragment.getActivity() != null) { 57 | messageButton.startAnimation(AnimationUtils.loadAnimation(fragment.getActivity(), R.anim.fade_in)); 58 | 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/ViewBehavioursOnPageChangeListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners; 2 | 3 | import android.support.v4.view.CustomViewPager; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import agency.tango.materialintroscreen.adapter.SlidesAdapter; 9 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 10 | 11 | public class ViewBehavioursOnPageChangeListener implements CustomViewPager.OnPageChangeListener { 12 | private final SlidesAdapter adapter; 13 | 14 | private List listeners = new ArrayList<>(); 15 | private List wrappers = new ArrayList<>(); 16 | private List pageScrolledListeners = new ArrayList<>(); 17 | 18 | public ViewBehavioursOnPageChangeListener(SlidesAdapter adapter) { 19 | this.adapter = adapter; 20 | } 21 | 22 | public ViewBehavioursOnPageChangeListener registerPageSelectedListener(IPageSelectedListener pageSelectedListener) { 23 | listeners.add(pageSelectedListener); 24 | return this; 25 | } 26 | 27 | public ViewBehavioursOnPageChangeListener registerViewTranslationWrapper(ViewTranslationWrapper wrapper) { 28 | wrappers.add(wrapper); 29 | return this; 30 | } 31 | 32 | public ViewBehavioursOnPageChangeListener registerOnPageScrolled(IPageScrolledListener pageScrolledListener) { 33 | pageScrolledListeners.add(pageScrolledListener); 34 | return this; 35 | } 36 | 37 | @Override 38 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 39 | if (isFirstSlide(position)) { 40 | for (ViewTranslationWrapper wrapper : wrappers) { 41 | wrapper.enterTranslate(positionOffset); 42 | } 43 | } else if (adapter.isLastSlide(position)) { 44 | for (ViewTranslationWrapper wrapper : wrappers) { 45 | wrapper.exitTranslate(positionOffset); 46 | } 47 | } else { 48 | for (ViewTranslationWrapper wrapper : wrappers) { 49 | wrapper.defaultTranslate(positionOffset); 50 | } 51 | } 52 | 53 | for (IPageScrolledListener pageScrolledListener : pageScrolledListeners) { 54 | pageScrolledListener.pageScrolled(position, positionOffset); 55 | } 56 | } 57 | 58 | @Override 59 | public void onPageSelected(int position) { 60 | for (IPageSelectedListener pageSelectedListener : listeners) { 61 | pageSelectedListener.pageSelected(position); 62 | } 63 | } 64 | 65 | @Override 66 | public void onPageScrollStateChanged(int state) { 67 | } 68 | 69 | private boolean isFirstSlide(int position) { 70 | return position == 0; 71 | } 72 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/clickListeners/PermissionNotGrantedClickListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners.clickListeners; 2 | 3 | import android.view.View; 4 | 5 | import agency.tango.materialintroscreen.MaterialIntroActivity; 6 | import agency.tango.materialintroscreen.animations.ViewTranslationWrapper; 7 | 8 | public class PermissionNotGrantedClickListener implements View.OnClickListener { 9 | private final MaterialIntroActivity activity; 10 | private final ViewTranslationWrapper translationWrapper; 11 | 12 | public PermissionNotGrantedClickListener(MaterialIntroActivity activity, ViewTranslationWrapper translationWrapper) { 13 | this.activity = activity; 14 | this.translationWrapper = translationWrapper; 15 | } 16 | 17 | @Override 18 | public void onClick(View v) { 19 | translationWrapper.error(); 20 | activity.showPermissionsNotGrantedError(); 21 | } 22 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/listeners/scrollListeners/ParallaxScrollListener.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.listeners.scrollListeners; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.support.v4.app.Fragment; 5 | 6 | import agency.tango.materialintroscreen.SlideFragment; 7 | import agency.tango.materialintroscreen.adapter.SlidesAdapter; 8 | import agency.tango.materialintroscreen.listeners.IPageScrolledListener; 9 | import agency.tango.materialintroscreen.parallax.Parallaxable; 10 | 11 | public class ParallaxScrollListener implements IPageScrolledListener { 12 | private SlidesAdapter adapter; 13 | 14 | public ParallaxScrollListener(SlidesAdapter adapter) { 15 | this.adapter = adapter; 16 | } 17 | 18 | @SuppressWarnings("ConstantConditions") 19 | @Override 20 | public void pageScrolled(int position, float offset) { 21 | if (position != adapter.getCount()) { 22 | Fragment fragment = adapter.getItem(position); 23 | Fragment fragmentNext = getNextFragment(position); 24 | 25 | if (fragment != null && fragment instanceof Parallaxable) { 26 | ((Parallaxable) fragment).setOffset(offset); 27 | } 28 | 29 | if (fragmentNext != null && fragment instanceof Parallaxable) { 30 | ((Parallaxable) fragmentNext).setOffset(offset - 1); 31 | } 32 | } 33 | } 34 | 35 | @Nullable 36 | private SlideFragment getNextFragment(int position) { 37 | if (position < adapter.getLastItemPosition()) { 38 | return adapter.getItem(position + 1); 39 | } 40 | return null; 41 | } 42 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFragment.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.parallax; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.FloatRange; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import java.util.LinkedList; 11 | import java.util.Queue; 12 | 13 | public class ParallaxFragment extends Fragment implements Parallaxable { 14 | @Nullable 15 | private Parallaxable parallaxLayout; 16 | 17 | @Override 18 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 19 | parallaxLayout = findParallaxLayout(view); 20 | } 21 | 22 | public Parallaxable findParallaxLayout(View root) { 23 | Queue queue = new LinkedList<>(); 24 | queue.add(root); 25 | 26 | while (!queue.isEmpty()) { 27 | View child = queue.remove(); 28 | 29 | if (child instanceof Parallaxable) { 30 | return (Parallaxable) child; 31 | } else if (child instanceof ViewGroup) { 32 | ViewGroup viewGroup = (ViewGroup) child; 33 | for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 34 | queue.add(viewGroup.getChildAt(i)); 35 | } 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | @Override 42 | public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { 43 | if (parallaxLayout != null) { 44 | parallaxLayout.setOffset(offset); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxFrameLayout.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.parallax; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.annotation.FloatRange; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.FrameLayout; 10 | 11 | import agency.tango.materialintroscreen.R; 12 | 13 | public class ParallaxFrameLayout extends FrameLayout implements Parallaxable { 14 | 15 | public ParallaxFrameLayout(Context context) { 16 | super(context); 17 | } 18 | 19 | public ParallaxFrameLayout(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | public ParallaxFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | } 26 | 27 | @Override 28 | protected boolean checkLayoutParams(ViewGroup.LayoutParams layoutParams) { 29 | return layoutParams instanceof LayoutParams; 30 | } 31 | 32 | @Override 33 | protected LayoutParams generateDefaultLayoutParams() { 34 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 35 | } 36 | 37 | @Override 38 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 39 | return new LayoutParams(getContext(), attrs); 40 | } 41 | 42 | @Override 43 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 44 | return new LayoutParams(p); 45 | } 46 | 47 | @Override 48 | public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { 49 | for (int i = getChildCount() - 1; i >= 0; i--) { 50 | View child = getChildAt(i); 51 | ParallaxFrameLayout.LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); 52 | if (layoutParams.parallaxFactor == 0) 53 | continue; 54 | child.setTranslationX(getWidth() * -offset * layoutParams.parallaxFactor); 55 | } 56 | } 57 | 58 | public static class LayoutParams extends FrameLayout.LayoutParams { 59 | float parallaxFactor = 0f; 60 | 61 | LayoutParams(Context context, AttributeSet attributeSet) { 62 | super(context, attributeSet); 63 | TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); 64 | parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); 65 | typedArray.recycle(); 66 | } 67 | 68 | LayoutParams(int width, int height) { 69 | super(width, height); 70 | } 71 | 72 | @SuppressWarnings("unused") 73 | LayoutParams(int width, int height, int gravity) { 74 | super(width, height, gravity); 75 | } 76 | 77 | LayoutParams(ViewGroup.LayoutParams source) { 78 | super(source); 79 | } 80 | 81 | @SuppressWarnings("unused") 82 | LayoutParams(MarginLayoutParams source) { 83 | super(source); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxLinearLayout.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.parallax; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.annotation.FloatRange; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | 11 | import agency.tango.materialintroscreen.R; 12 | 13 | public class ParallaxLinearLayout extends LinearLayout implements Parallaxable { 14 | 15 | public ParallaxLinearLayout(Context context) { 16 | super(context); 17 | } 18 | 19 | public ParallaxLinearLayout(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | public ParallaxLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | } 26 | 27 | @Override 28 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 29 | return p instanceof LayoutParams; 30 | } 31 | 32 | @Override 33 | protected LayoutParams generateDefaultLayoutParams() { 34 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 35 | } 36 | 37 | @Override 38 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 39 | return new LayoutParams(getContext(), attrs); 40 | } 41 | 42 | @Override 43 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 44 | return new LayoutParams(p); 45 | } 46 | 47 | @Override 48 | public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { 49 | for (int i = getChildCount() - 1; i >= 0; i--) { 50 | View child = getChildAt(i); 51 | ParallaxLinearLayout.LayoutParams p = (LayoutParams) child.getLayoutParams(); 52 | if (p.parallaxFactor == 0) 53 | continue; 54 | child.setTranslationX(getWidth() * -offset * p.parallaxFactor); 55 | } 56 | } 57 | 58 | public static class LayoutParams extends LinearLayout.LayoutParams { 59 | float parallaxFactor = 0f; 60 | 61 | LayoutParams(Context context, AttributeSet attributeSet) { 62 | super(context, attributeSet); 63 | TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); 64 | parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); 65 | typedArray.recycle(); 66 | } 67 | 68 | LayoutParams(int width, int height) { 69 | super(width, height); 70 | } 71 | 72 | @SuppressWarnings("unused") 73 | LayoutParams(int width, int height, int gravity) { 74 | super(width, height, gravity); 75 | } 76 | 77 | LayoutParams(ViewGroup.LayoutParams source) { 78 | super(source); 79 | } 80 | 81 | @SuppressWarnings("unused") 82 | LayoutParams(MarginLayoutParams source) { 83 | super(source); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/ParallaxRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.parallax; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.annotation.FloatRange; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.RelativeLayout; 10 | 11 | import agency.tango.materialintroscreen.R; 12 | 13 | public class ParallaxRelativeLayout extends RelativeLayout implements Parallaxable { 14 | 15 | public ParallaxRelativeLayout(Context context) { 16 | super(context); 17 | } 18 | 19 | public ParallaxRelativeLayout(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | public ParallaxRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | } 26 | 27 | @Override 28 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 29 | return p instanceof LayoutParams; 30 | } 31 | 32 | @Override 33 | protected LayoutParams generateDefaultLayoutParams() { 34 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 35 | } 36 | 37 | @Override 38 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 39 | return new LayoutParams(getContext(), attrs); 40 | } 41 | 42 | @Override 43 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 44 | return new LayoutParams(p); 45 | } 46 | 47 | @Override 48 | public void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset) { 49 | for (int i = getChildCount() - 1; i >= 0; i--) { 50 | View child = getChildAt(i); 51 | ParallaxRelativeLayout.LayoutParams p = (LayoutParams) child.getLayoutParams(); 52 | if (p.parallaxFactor == 0) 53 | continue; 54 | child.setTranslationX(getWidth() * -offset * p.parallaxFactor); 55 | } 56 | } 57 | 58 | public static class LayoutParams extends RelativeLayout.LayoutParams { 59 | float parallaxFactor = 0f; 60 | 61 | LayoutParams(Context context, AttributeSet attributeSet) { 62 | super(context, attributeSet); 63 | TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ParallaxLayout_Layout); 64 | parallaxFactor = typedArray.getFloat(R.styleable.ParallaxLayout_Layout_layout_parallaxFactor, parallaxFactor); 65 | typedArray.recycle(); 66 | } 67 | 68 | LayoutParams(int width, int height) { 69 | super(width, height); 70 | } 71 | 72 | LayoutParams(ViewGroup.LayoutParams source) { 73 | super(source); 74 | } 75 | 76 | @SuppressWarnings("unused") 77 | LayoutParams(MarginLayoutParams source) { 78 | super(source); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/parallax/Parallaxable.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.parallax; 2 | 3 | import android.support.annotation.FloatRange; 4 | 5 | public interface Parallaxable { 6 | void setOffset(@FloatRange(from = -1.0, to = 1.0) float offset); 7 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/InkPageIndicator.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.widgets; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.annotation.SuppressLint; 7 | import android.content.Context; 8 | import android.content.res.TypedArray; 9 | import android.database.DataSetObserver; 10 | import android.graphics.Canvas; 11 | import android.graphics.Paint; 12 | import android.graphics.Path; 13 | import android.graphics.RectF; 14 | import android.os.Parcel; 15 | import android.os.Parcelable; 16 | import android.support.v4.view.ViewCompat; 17 | import android.support.v4.view.CustomViewPager; 18 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 19 | import android.util.AttributeSet; 20 | import android.view.View; 21 | import android.view.animation.Interpolator; 22 | 23 | import java.util.Arrays; 24 | 25 | import agency.tango.materialintroscreen.R; 26 | 27 | public class InkPageIndicator extends View implements CustomViewPager.OnPageChangeListener, View.OnAttachStateChangeListener { 28 | private static final int DEFAULT_DOT_SIZE = 8; 29 | private static final int DEFAULT_GAP = 12; 30 | private static final int DEFAULT_ANIM_DURATION = 400; 31 | private static final int DEFAULT_UNSELECTED_COLOUR = 0x80ffffff; 32 | private static final int DEFAULT_SELECTED_COLOUR = 0xffffffff; 33 | 34 | private static final float INVALID_FRACTION = -1f; 35 | private static final float MINIMAL_REVEAL = 0.00001f; 36 | private final Paint selectedPaint; 37 | private final Path unselectedDotPath; 38 | private final Path unselectedDotLeftPath; 39 | private final Path unselectedDotRightPath; 40 | private final RectF rectF; 41 | private final Interpolator interpolator; 42 | float endX1; 43 | float endY1; 44 | float endX2; 45 | float endY2; 46 | float controlX1; 47 | float controlY1; 48 | float controlX2; 49 | float controlY2; 50 | private int dotDiameter; 51 | private int gap; 52 | private long animDuration; 53 | private int unselectedColour; 54 | private float dotRadius; 55 | private float halfDotRadius; 56 | private long animHalfDuration; 57 | private float dotTopY; 58 | private float dotCenterY; 59 | private float dotBottomY; 60 | private SwipeableViewPager viewPager; 61 | private int pageCount; 62 | private int currentPage; 63 | private int previousPage; 64 | private float selectedDotX; 65 | private boolean selectedDotInPosition; 66 | private float[] dotCenterX; 67 | private float[] joiningFractions; 68 | private float retreatingJoinX1; 69 | private float retreatingJoinX2; 70 | private float[] dotRevealFractions; 71 | private boolean isAttachedToWindow; 72 | private boolean pageChanging; 73 | private Paint unselectedPaint; 74 | private Path combinedUnselectedPath; 75 | private ValueAnimator moveAnimation; 76 | private PendingRetreatAnimator retreatAnimation; 77 | private PendingRevealAnimator[] revealAnimations; 78 | 79 | public InkPageIndicator(Context context) { 80 | this(context, null, 0); 81 | } 82 | 83 | public InkPageIndicator(Context context, AttributeSet attrs) { 84 | this(context, attrs, 0); 85 | } 86 | 87 | public InkPageIndicator(Context context, AttributeSet attrs, int defStyle) { 88 | super(context, attrs, defStyle); 89 | 90 | final int density = (int) context.getResources().getDisplayMetrics().density; 91 | 92 | final TypedArray typedArray = getContext().obtainStyledAttributes( 93 | attrs, R.styleable.InkPageIndicator, defStyle, 0); 94 | 95 | dotDiameter = typedArray.getDimensionPixelSize(R.styleable.InkPageIndicator_dotDiameter, DEFAULT_DOT_SIZE * density); 96 | dotRadius = dotDiameter / 2; 97 | halfDotRadius = dotRadius / 2; 98 | gap = typedArray.getDimensionPixelSize(R.styleable.InkPageIndicator_dotGap, DEFAULT_GAP * density); 99 | animDuration = (long) typedArray.getInteger(R.styleable.InkPageIndicator_animationDuration, DEFAULT_ANIM_DURATION); 100 | animHalfDuration = animDuration / 2; 101 | unselectedColour = typedArray.getColor(R.styleable.InkPageIndicator_pageIndicatorColor, DEFAULT_UNSELECTED_COLOUR); 102 | int selectedColour = typedArray.getColor(R.styleable.InkPageIndicator_currentPageIndicatorColor, DEFAULT_SELECTED_COLOUR); 103 | typedArray.recycle(); 104 | 105 | unselectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 106 | unselectedPaint.setColor(unselectedColour); 107 | selectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 108 | selectedPaint.setColor(selectedColour); 109 | interpolator = new FastOutSlowInInterpolator(); 110 | 111 | combinedUnselectedPath = new Path(); 112 | unselectedDotPath = new Path(); 113 | unselectedDotLeftPath = new Path(); 114 | unselectedDotRightPath = new Path(); 115 | rectF = new RectF(); 116 | 117 | addOnAttachStateChangeListener(this); 118 | } 119 | 120 | private int getCount() { 121 | return viewPager.getAdapter().getCount(); 122 | } 123 | 124 | public void setViewPager(final SwipeableViewPager viewPager) { 125 | this.viewPager = viewPager; 126 | viewPager.addOnPageChangeListener(this); 127 | setPageCount(getCount()); 128 | viewPager.getAdapter().registerDataSetObserver(new DataSetObserver() { 129 | @Override 130 | public void onChanged() { 131 | setPageCount(getCount()); 132 | } 133 | }); 134 | setCurrentPageImmediate(); 135 | } 136 | 137 | @Override 138 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 139 | if (isAttachedToWindow) { 140 | float fraction = positionOffset; 141 | int currentPosition = pageChanging ? previousPage : currentPage; 142 | int leftDotPosition = position; 143 | 144 | if (currentPosition != position) { 145 | fraction = 1f - positionOffset; 146 | 147 | if (fraction == 1f) { 148 | leftDotPosition = Math.min(currentPosition, position); 149 | } 150 | } 151 | setJoiningFraction(leftDotPosition, fraction); 152 | } 153 | } 154 | 155 | @Override 156 | public void onPageSelected(int position) { 157 | if (position < pageCount) { 158 | if (isAttachedToWindow) { 159 | setSelectedPage(position); 160 | } else { 161 | setCurrentPageImmediate(); 162 | } 163 | } 164 | } 165 | 166 | @Override 167 | public void onPageScrollStateChanged(int state) { 168 | } 169 | 170 | private void setPageCount(int pages) { 171 | if (pages > 0) { 172 | pageCount = pages; 173 | resetState(); 174 | requestLayout(); 175 | } 176 | } 177 | 178 | private void calculateDotPositions(int width) { 179 | int left = getPaddingLeft(); 180 | int top = getPaddingTop(); 181 | int right = width - getPaddingRight(); 182 | 183 | int requiredWidth = getRequiredWidth(); 184 | float startLeft = left + ((right - left - requiredWidth) / 2) + dotRadius; 185 | 186 | dotCenterX = new float[pageCount]; 187 | for (int i = 0; i < pageCount; i++) { 188 | dotCenterX[i] = startLeft + i * (dotDiameter + gap); 189 | } 190 | dotTopY = top; 191 | dotCenterY = top + dotRadius; 192 | dotBottomY = top + dotDiameter; 193 | 194 | setCurrentPageImmediate(); 195 | } 196 | 197 | private void setCurrentPageImmediate() { 198 | if (viewPager != null) { 199 | currentPage = viewPager.getCurrentItem(); 200 | } else { 201 | currentPage = 0; 202 | } 203 | if (isDotAnimationStarted()) { 204 | selectedDotX = dotCenterX[currentPage]; 205 | } 206 | } 207 | 208 | private boolean isDotAnimationStarted() { 209 | return dotCenterX != null && dotCenterX.length > 0 && (moveAnimation == null || !moveAnimation.isStarted()); 210 | } 211 | 212 | private void resetState() { 213 | joiningFractions = new float[pageCount - 1]; 214 | Arrays.fill(joiningFractions, 0f); 215 | dotRevealFractions = new float[pageCount]; 216 | Arrays.fill(dotRevealFractions, 0f); 217 | retreatingJoinX1 = INVALID_FRACTION; 218 | retreatingJoinX2 = INVALID_FRACTION; 219 | selectedDotInPosition = true; 220 | } 221 | 222 | @SuppressLint("SwitchIntDef") 223 | @Override 224 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 225 | int desiredHeight = getDesiredHeight(); 226 | int height; 227 | switch (MeasureSpec.getMode(heightMeasureSpec)) { 228 | case MeasureSpec.EXACTLY: 229 | height = MeasureSpec.getSize(heightMeasureSpec); 230 | break; 231 | case MeasureSpec.AT_MOST: 232 | height = Math.min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec)); 233 | break; 234 | default: 235 | height = desiredHeight; 236 | break; 237 | } 238 | 239 | int desiredWidth = getDesiredWidth(); 240 | int width; 241 | switch (MeasureSpec.getMode(widthMeasureSpec)) { 242 | case MeasureSpec.EXACTLY: 243 | width = MeasureSpec.getSize(widthMeasureSpec); 244 | break; 245 | case MeasureSpec.AT_MOST: 246 | width = Math.min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec)); 247 | break; 248 | default: 249 | width = desiredWidth; 250 | break; 251 | } 252 | setMeasuredDimension(width, height); 253 | calculateDotPositions(width); 254 | } 255 | 256 | private int getDesiredHeight() { 257 | return getPaddingTop() + dotDiameter + getPaddingBottom(); 258 | } 259 | 260 | private int getRequiredWidth() { 261 | return pageCount * dotDiameter + (pageCount - 1) * gap; 262 | } 263 | 264 | private int getDesiredWidth() { 265 | return getPaddingLeft() + getRequiredWidth() + getPaddingRight(); 266 | } 267 | 268 | @Override 269 | public void onViewAttachedToWindow(View view) { 270 | isAttachedToWindow = true; 271 | } 272 | 273 | @Override 274 | public void onViewDetachedFromWindow(View view) { 275 | isAttachedToWindow = false; 276 | } 277 | 278 | @Override 279 | protected void onDraw(Canvas canvas) { 280 | if (viewPager == null || pageCount == 0) return; 281 | drawUnselected(canvas); 282 | drawSelected(canvas); 283 | } 284 | 285 | private void drawUnselected(Canvas canvas) { 286 | combinedUnselectedPath.rewind(); 287 | 288 | for (int page = 0; page < pageCount; page++) { 289 | int nextXIndex; 290 | 291 | if (page == pageCount - 1) { 292 | nextXIndex = page; 293 | } else { 294 | nextXIndex = page + 1; 295 | } 296 | 297 | Path unselectedPath = getUnselectedPath(page, 298 | dotCenterX[page], 299 | dotCenterX[nextXIndex], 300 | page == pageCount - 1 ? INVALID_FRACTION : joiningFractions[page], 301 | dotRevealFractions[page]); 302 | 303 | unselectedPath.addPath(combinedUnselectedPath); 304 | combinedUnselectedPath.addPath(unselectedPath); 305 | } 306 | 307 | if (retreatingJoinX1 != INVALID_FRACTION) { 308 | Path retreatingJoinPath = getRetreatingJoinPath(); 309 | combinedUnselectedPath.addPath(retreatingJoinPath); 310 | } 311 | 312 | canvas.drawPath(combinedUnselectedPath, unselectedPaint); 313 | } 314 | 315 | private Path getUnselectedPath(int page, float centerX, float nextCenterX, float joiningFraction, float dotRevealFraction) { 316 | unselectedDotPath.rewind(); 317 | 318 | if (isDotNotJoining(page, joiningFraction, dotRevealFraction)) { 319 | unselectedDotPath.addCircle(dotCenterX[page], dotCenterY, dotRadius, Path.Direction.CW); 320 | } 321 | 322 | if (isDotJoining(joiningFraction)) { 323 | 324 | unselectedDotLeftPath.rewind(); 325 | unselectedDotLeftPath.moveTo(centerX, dotBottomY); 326 | rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); 327 | unselectedDotLeftPath.arcTo(rectF, 90, 180, true); 328 | 329 | endX1 = centerX + dotRadius + (joiningFraction * gap); 330 | endY1 = dotCenterY; 331 | controlX1 = centerX + halfDotRadius; 332 | controlY1 = dotTopY; 333 | controlX2 = endX1; 334 | controlY2 = endY1 - halfDotRadius; 335 | unselectedDotLeftPath.cubicTo(controlX1, controlY1, 336 | controlX2, controlY2, 337 | endX1, endY1); 338 | 339 | endX2 = centerX; 340 | endY2 = dotBottomY; 341 | controlX1 = endX1; 342 | controlY1 = endY1 + halfDotRadius; 343 | controlX2 = centerX + halfDotRadius; 344 | controlY2 = dotBottomY; 345 | unselectedDotLeftPath.cubicTo(controlX1, controlY1, 346 | controlX2, controlY2, 347 | endX2, endY2); 348 | 349 | unselectedDotPath.addPath(unselectedDotLeftPath); 350 | 351 | unselectedDotRightPath.rewind(); 352 | unselectedDotRightPath.moveTo(nextCenterX, dotBottomY); 353 | 354 | rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); 355 | unselectedDotRightPath.arcTo(rectF, 90, -180, true); 356 | 357 | endX1 = nextCenterX - dotRadius - (joiningFraction * gap); 358 | endY1 = dotCenterY; 359 | controlX1 = nextCenterX - halfDotRadius; 360 | controlY1 = dotTopY; 361 | controlX2 = endX1; 362 | controlY2 = endY1 - halfDotRadius; 363 | unselectedDotRightPath.cubicTo(controlX1, controlY1, 364 | controlX2, controlY2, 365 | endX1, endY1); 366 | 367 | endX2 = nextCenterX; 368 | endY2 = dotBottomY; 369 | controlX1 = endX1; 370 | controlY1 = endY1 + halfDotRadius; 371 | controlX2 = endX2 - halfDotRadius; 372 | controlY2 = dotBottomY; 373 | unselectedDotRightPath.cubicTo(controlX1, controlY1, 374 | controlX2, controlY2, 375 | endX2, endY2); 376 | unselectedDotPath.addPath(unselectedDotRightPath); 377 | } 378 | 379 | if (joiningFraction > 0.5f && joiningFraction < 1f && retreatingJoinX1 == INVALID_FRACTION) { 380 | float adjustedFraction = (joiningFraction - 0.2f) * 1.25f; 381 | 382 | unselectedDotPath.moveTo(centerX, dotBottomY); 383 | rectF.set(centerX - dotRadius, dotTopY, centerX + dotRadius, dotBottomY); 384 | unselectedDotPath.arcTo(rectF, 90, 180, true); 385 | 386 | endX1 = centerX + dotRadius + (gap / 2); 387 | endY1 = dotCenterY - (adjustedFraction * dotRadius); 388 | controlX1 = endX1 - (adjustedFraction * dotRadius); 389 | controlY1 = dotTopY; 390 | controlX2 = endX1 - ((1 - adjustedFraction) * dotRadius); 391 | controlY2 = endY1; 392 | unselectedDotPath.cubicTo(controlX1, controlY1, 393 | controlX2, controlY2, 394 | endX1, endY1); 395 | 396 | endX2 = nextCenterX; 397 | endY2 = dotTopY; 398 | controlX1 = endX1 + ((1 - adjustedFraction) * dotRadius); 399 | controlY1 = endY1; 400 | controlX2 = endX1 + (adjustedFraction * dotRadius); 401 | controlY2 = dotTopY; 402 | unselectedDotPath.cubicTo(controlX1, controlY1, 403 | controlX2, controlY2, 404 | endX2, endY2); 405 | 406 | rectF.set(nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); 407 | unselectedDotPath.arcTo(rectF, 270, 180, true); 408 | 409 | endY1 = dotCenterY + (adjustedFraction * dotRadius); 410 | controlX1 = endX1 + (adjustedFraction * dotRadius); 411 | controlY1 = dotBottomY; 412 | controlX2 = endX1 + ((1 - adjustedFraction) * dotRadius); 413 | controlY2 = endY1; 414 | unselectedDotPath.cubicTo(controlX1, controlY1, 415 | controlX2, controlY2, 416 | endX1, endY1); 417 | 418 | endX2 = centerX; 419 | endY2 = dotBottomY; 420 | controlX1 = endX1 - ((1 - adjustedFraction) * dotRadius); 421 | controlY1 = endY1; 422 | controlX2 = endX1 - (adjustedFraction * dotRadius); 423 | controlY2 = endY2; 424 | unselectedDotPath.cubicTo(controlX1, controlY1, 425 | controlX2, controlY2, 426 | endX2, endY2); 427 | } 428 | if (joiningFraction == 1 && retreatingJoinX1 == INVALID_FRACTION) { 429 | rectF.set(centerX - dotRadius, dotTopY, nextCenterX + dotRadius, dotBottomY); 430 | unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW); 431 | } 432 | 433 | if (dotRevealFraction > MINIMAL_REVEAL) { 434 | unselectedDotPath.addCircle(centerX, dotCenterY, dotRevealFraction * dotRadius, 435 | Path.Direction.CW); 436 | } 437 | 438 | return unselectedDotPath; 439 | } 440 | 441 | private boolean isDotJoining(float joiningFraction) { 442 | return joiningFraction > 0f && joiningFraction <= 0.5f && retreatingJoinX1 == INVALID_FRACTION; 443 | } 444 | 445 | private boolean isDotNotJoining(int page, float joiningFraction, float dotRevealFraction) { 446 | return (joiningFraction == 0f || joiningFraction == INVALID_FRACTION) 447 | && dotRevealFraction == 0f 448 | && !(page == currentPage && selectedDotInPosition); 449 | } 450 | 451 | private Path getRetreatingJoinPath() { 452 | unselectedDotPath.rewind(); 453 | rectF.set(retreatingJoinX1, dotTopY, retreatingJoinX2, dotBottomY); 454 | unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW); 455 | return unselectedDotPath; 456 | } 457 | 458 | private void drawSelected(Canvas canvas) { 459 | canvas.drawCircle(selectedDotX, dotCenterY, dotRadius, selectedPaint); 460 | } 461 | 462 | private void setSelectedPage(int now) { 463 | if (now == currentPage) { 464 | return; 465 | } 466 | 467 | pageChanging = true; 468 | previousPage = currentPage; 469 | currentPage = now; 470 | final int steps = Math.abs(now - previousPage); 471 | 472 | if (steps > 1) { 473 | if (now > previousPage) { 474 | for (int i = 0; i < steps; i++) { 475 | setJoiningFraction(previousPage + i, 1f); 476 | } 477 | } else { 478 | for (int i = -1; i > -steps; i--) { 479 | setJoiningFraction(previousPage + i, 1f); 480 | } 481 | } 482 | } 483 | 484 | moveAnimation = createMoveSelectedAnimator(dotCenterX[now], previousPage, now, steps); 485 | moveAnimation.start(); 486 | } 487 | 488 | private ValueAnimator createMoveSelectedAnimator( 489 | final float moveTo, int was, int now, int steps) { 490 | ValueAnimator moveSelected = ValueAnimator.ofFloat(selectedDotX, moveTo); 491 | 492 | retreatAnimation = new PendingRetreatAnimator(was, now, steps, 493 | now > was ? 494 | new RightwardStartPredicate(moveTo - ((moveTo - selectedDotX) * 0.25f)) : 495 | new LeftwardStartPredicate(moveTo + ((selectedDotX - moveTo) * 0.25f))); 496 | retreatAnimation.addListener(new AnimatorListenerAdapter() { 497 | @Override 498 | public void onAnimationEnd(Animator animation) { 499 | resetState(); 500 | pageChanging = false; 501 | } 502 | }); 503 | moveSelected.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 504 | @Override 505 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 506 | selectedDotX = (Float) valueAnimator.getAnimatedValue(); 507 | retreatAnimation.startIfNecessary(selectedDotX); 508 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 509 | } 510 | }); 511 | moveSelected.addListener(new AnimatorListenerAdapter() { 512 | @Override 513 | public void onAnimationStart(Animator animation) { 514 | selectedDotInPosition = false; 515 | } 516 | 517 | @Override 518 | public void onAnimationEnd(Animator animation) { 519 | selectedDotInPosition = true; 520 | } 521 | }); 522 | 523 | moveSelected.setStartDelay(selectedDotInPosition ? animDuration / 4L : 0L); 524 | moveSelected.setDuration(animDuration * 3L / 4L); 525 | moveSelected.setInterpolator(interpolator); 526 | return moveSelected; 527 | } 528 | 529 | private void setJoiningFraction(int leftDot, float fraction) { 530 | if (joiningFractions != null) { 531 | if (leftDot < joiningFractions.length) { 532 | joiningFractions[leftDot] = fraction; 533 | ViewCompat.postInvalidateOnAnimation(this); 534 | } 535 | } 536 | } 537 | 538 | public void clearJoiningFractions() { 539 | Arrays.fill(joiningFractions, 0f); 540 | ViewCompat.postInvalidateOnAnimation(this); 541 | } 542 | 543 | private void setDotRevealFraction(int dot, float fraction) { 544 | if (dot < dotRevealFractions.length) { 545 | dotRevealFractions[dot] = fraction; 546 | } 547 | ViewCompat.postInvalidateOnAnimation(this); 548 | } 549 | 550 | public void setPageIndicatorColor(int secondaryColor) { 551 | unselectedColour = secondaryColor; 552 | unselectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 553 | unselectedPaint.setColor(unselectedColour); 554 | } 555 | 556 | @Override 557 | public void onRestoreInstanceState(Parcelable state) { 558 | SavedState savedState = (SavedState) state; 559 | super.onRestoreInstanceState(savedState.getSuperState()); 560 | currentPage = savedState.currentPage; 561 | requestLayout(); 562 | } 563 | 564 | @Override 565 | public Parcelable onSaveInstanceState() { 566 | Parcelable superState = super.onSaveInstanceState(); 567 | SavedState savedState = new SavedState(superState); 568 | savedState.currentPage = currentPage; 569 | return savedState; 570 | } 571 | 572 | static class SavedState extends BaseSavedState { 573 | public static final Creator CREATOR = new Creator() { 574 | @Override 575 | public SavedState createFromParcel(Parcel in) { 576 | return new SavedState(in); 577 | } 578 | 579 | @Override 580 | public SavedState[] newArray(int size) { 581 | return new SavedState[size]; 582 | } 583 | }; 584 | int currentPage; 585 | 586 | SavedState(Parcelable superState) { 587 | super(superState); 588 | } 589 | 590 | private SavedState(Parcel in) { 591 | super(in); 592 | currentPage = in.readInt(); 593 | } 594 | 595 | @Override 596 | public void writeToParcel(Parcel dest, int flags) { 597 | super.writeToParcel(dest, flags); 598 | dest.writeInt(currentPage); 599 | } 600 | } 601 | 602 | public abstract class PendingStartAnimator extends ValueAnimator { 603 | boolean hasStarted; 604 | StartPredicate predicate; 605 | 606 | PendingStartAnimator(StartPredicate predicate) { 607 | super(); 608 | this.predicate = predicate; 609 | hasStarted = false; 610 | } 611 | 612 | void startIfNecessary(float currentValue) { 613 | if (!hasStarted && predicate.shouldStart(currentValue)) { 614 | start(); 615 | hasStarted = true; 616 | } 617 | } 618 | } 619 | 620 | public class PendingRetreatAnimator extends PendingStartAnimator { 621 | PendingRetreatAnimator(int was, int now, int steps, StartPredicate predicate) { 622 | super(predicate); 623 | setDuration(animHalfDuration); 624 | setInterpolator(interpolator); 625 | 626 | // work out the start/end values of the retreating join from the direction we're 627 | // travelling in. Also look at the current selected dot position, i.e. we're moving on 628 | // before a prior anim has finished. 629 | final float initialX1 = now > was ? Math.min(dotCenterX[was], selectedDotX) - dotRadius 630 | : dotCenterX[now] - dotRadius; 631 | final float finalX1 = now > was ? dotCenterX[now] - dotRadius 632 | : dotCenterX[now] - dotRadius; 633 | final float initialX2 = now > was ? dotCenterX[now] + dotRadius 634 | : Math.max(dotCenterX[was], selectedDotX) + dotRadius; 635 | final float finalX2 = now > was ? dotCenterX[now] + dotRadius 636 | : dotCenterX[now] + dotRadius; 637 | 638 | revealAnimations = new PendingRevealAnimator[steps]; 639 | // hold on to the indexes of the dots that will be hidden by the retreat so that 640 | // we can initialize their revealFraction's i.e. make sure they're hidden while the 641 | // reveal animation runs 642 | final int[] dotsToHide = new int[steps]; 643 | if (initialX1 != finalX1) { 644 | setFloatValues(initialX1, finalX1); 645 | for (int i = 0; i < steps; i++) { 646 | revealAnimations[i] = new PendingRevealAnimator(was + i, 647 | new RightwardStartPredicate(dotCenterX[was + i])); 648 | dotsToHide[i] = was + i; 649 | } 650 | addUpdateListener(new AnimatorUpdateListener() { 651 | @Override 652 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 653 | retreatingJoinX1 = (Float) valueAnimator.getAnimatedValue(); 654 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 655 | 656 | for (PendingRevealAnimator pendingReveal : revealAnimations) { 657 | pendingReveal.startIfNecessary(retreatingJoinX1); 658 | } 659 | } 660 | }); 661 | } else { 662 | setFloatValues(initialX2, finalX2); 663 | for (int i = 0; i < steps; i++) { 664 | revealAnimations[i] = new PendingRevealAnimator(was - i, 665 | new LeftwardStartPredicate(dotCenterX[was - i])); 666 | dotsToHide[i] = was - i; 667 | } 668 | addUpdateListener(new AnimatorUpdateListener() { 669 | @Override 670 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 671 | retreatingJoinX2 = (Float) valueAnimator.getAnimatedValue(); 672 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 673 | 674 | for (PendingRevealAnimator pendingReveal : revealAnimations) { 675 | pendingReveal.startIfNecessary(retreatingJoinX2); 676 | } 677 | } 678 | }); 679 | } 680 | 681 | addListener(new AnimatorListenerAdapter() { 682 | @Override 683 | public void onAnimationStart(Animator animation) { 684 | clearJoiningFractions(); 685 | 686 | for (int dot : dotsToHide) { 687 | setDotRevealFraction(dot, MINIMAL_REVEAL); 688 | } 689 | retreatingJoinX1 = initialX1; 690 | retreatingJoinX2 = initialX2; 691 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 692 | } 693 | 694 | @Override 695 | public void onAnimationEnd(Animator animation) { 696 | retreatingJoinX1 = INVALID_FRACTION; 697 | retreatingJoinX2 = INVALID_FRACTION; 698 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 699 | } 700 | }); 701 | } 702 | } 703 | 704 | public class PendingRevealAnimator extends PendingStartAnimator { 705 | private int dot; 706 | 707 | PendingRevealAnimator(int dot, StartPredicate predicate) { 708 | super(predicate); 709 | setFloatValues(MINIMAL_REVEAL, 1f); 710 | this.dot = dot; 711 | setDuration(animHalfDuration); 712 | setInterpolator(interpolator); 713 | addUpdateListener(new AnimatorUpdateListener() { 714 | @Override 715 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 716 | setDotRevealFraction(PendingRevealAnimator.this.dot, 717 | (Float) valueAnimator.getAnimatedValue()); 718 | } 719 | }); 720 | addListener(new AnimatorListenerAdapter() { 721 | @Override 722 | public void onAnimationEnd(Animator animation) { 723 | setDotRevealFraction(PendingRevealAnimator.this.dot, 0f); 724 | ViewCompat.postInvalidateOnAnimation(InkPageIndicator.this); 725 | } 726 | }); 727 | } 728 | } 729 | 730 | public abstract class StartPredicate { 731 | float thresholdValue; 732 | 733 | StartPredicate(float thresholdValue) { 734 | this.thresholdValue = thresholdValue; 735 | } 736 | 737 | abstract boolean shouldStart(float currentValue); 738 | } 739 | 740 | public class RightwardStartPredicate extends StartPredicate { 741 | RightwardStartPredicate(float thresholdValue) { 742 | super(thresholdValue); 743 | } 744 | 745 | boolean shouldStart(float currentValue) { 746 | return currentValue > thresholdValue; 747 | } 748 | } 749 | 750 | public class LeftwardStartPredicate extends StartPredicate { 751 | LeftwardStartPredicate(float thresholdValue) { 752 | super(thresholdValue); 753 | } 754 | 755 | boolean shouldStart(float currentValue) { 756 | return currentValue < thresholdValue; 757 | } 758 | } 759 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/OverScrollViewPager.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.support.v4.view.ViewCompat; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.ViewConfiguration; 9 | import android.view.animation.AccelerateInterpolator; 10 | import android.view.animation.Interpolator; 11 | import android.widget.RelativeLayout; 12 | 13 | import agency.tango.materialintroscreen.R; 14 | import agency.tango.materialintroscreen.listeners.IFinishListener; 15 | 16 | public class OverScrollViewPager extends RelativeLayout { 17 | private SwipeableViewPager swipeableViewPager = null; 18 | private boolean mIsBeingDragged = false; 19 | private float mMotionBeginX = 0; 20 | private float positionOffset = 0; 21 | private int mTouchSlop; 22 | private IFinishListener finishListener; 23 | 24 | public OverScrollViewPager(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public OverScrollViewPager(Context context, AttributeSet attrs) { 29 | this(context, attrs, 0); 30 | } 31 | 32 | public OverScrollViewPager(Context context, AttributeSet attrs, int defStyle) { 33 | super(context, attrs, defStyle); 34 | 35 | swipeableViewPager = createOverScrollView(); 36 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( 37 | RelativeLayout.LayoutParams.MATCH_PARENT, 38 | RelativeLayout.LayoutParams.MATCH_PARENT); 39 | addView(swipeableViewPager, layoutParams); 40 | 41 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 42 | } 43 | 44 | @Override 45 | public boolean onInterceptTouchEvent(MotionEvent event) { 46 | int action = event.getAction(); 47 | 48 | if (action == MotionEvent.ACTION_DOWN) { 49 | mMotionBeginX = event.getX(); 50 | mIsBeingDragged = false; 51 | } else if (action == MotionEvent.ACTION_MOVE) { 52 | if (!mIsBeingDragged) { 53 | float scrollDirectionDiff = event.getX() - mMotionBeginX; 54 | 55 | if (Math.abs(scrollDirectionDiff) > mTouchSlop) { 56 | if (canOverScrollAtEnd() && scrollDirectionDiff < 0f) { 57 | mIsBeingDragged = true; 58 | } 59 | } 60 | } 61 | } 62 | 63 | return mIsBeingDragged; 64 | } 65 | 66 | @Override 67 | public boolean onTouchEvent(MotionEvent event) { 68 | int action = event.getAction(); 69 | float moveOffset = event.getX() - mMotionBeginX; 70 | 71 | if (action == MotionEvent.ACTION_MOVE) { 72 | moveOverScrollView(moveOffset); 73 | } else if (action == MotionEvent.ACTION_UP) { 74 | if (positionOffset > 0.5f) { 75 | finishOverScrollViewWithAnimation(moveOffset); 76 | } else { 77 | resetOverScrollViewWithAnimation(moveOffset); 78 | } 79 | mIsBeingDragged = false; 80 | } 81 | 82 | return true; 83 | } 84 | 85 | public SwipeableViewPager getOverScrollView() { 86 | return swipeableViewPager; 87 | } 88 | 89 | public void registerFinishListener(IFinishListener listener) { 90 | finishListener = listener; 91 | } 92 | 93 | private void moveOverScrollView(float currentX) { 94 | if (canScroll(currentX)) { 95 | scrollTo((int) -currentX, 0); 96 | 97 | positionOffset = calculateOffset(); 98 | swipeableViewPager.onPageScrolled(swipeableViewPager.getAdapter().getLastItemPosition(), positionOffset, 0); 99 | 100 | if (shouldFinish()) { 101 | finishListener.doOnFinish(); 102 | } 103 | } 104 | } 105 | 106 | private float calculateOffset() { 107 | return ((100f * getScrollX()) / getWidth()) / 100f; 108 | } 109 | 110 | private boolean shouldFinish() { 111 | return positionOffset == 1f; 112 | } 113 | 114 | private boolean canScroll(float currentX) { 115 | return currentX <= 0f; 116 | } 117 | 118 | private void resetOverScrollViewWithAnimation(final float currentX) { 119 | post(new SmoothScrollRunnable((int) currentX, 0, 300, new AccelerateInterpolator())); 120 | } 121 | 122 | private void finishOverScrollViewWithAnimation(float currentX) { 123 | post(new SmoothScrollRunnable((int) currentX, -getWidth(), 300, new AccelerateInterpolator())); 124 | } 125 | 126 | private boolean canOverScrollAtEnd() { 127 | SwipeableViewPager viewPager = getOverScrollView(); 128 | PagerAdapter adapter = viewPager.getAdapter(); 129 | if (null != adapter && adapter.getCount() > 0) { 130 | if (viewPager.alphaExitTransitionEnabled() && viewPager.getCurrentItem() == adapter.getCount() - 1) { 131 | return true; 132 | } 133 | return false; 134 | } 135 | 136 | return false; 137 | } 138 | 139 | private SwipeableViewPager createOverScrollView() { 140 | SwipeableViewPager swipeableViewPager = new SwipeableViewPager(getContext(), null); 141 | swipeableViewPager.setId(R.id.swipeable_view_pager); 142 | return swipeableViewPager; 143 | } 144 | 145 | final class SmoothScrollRunnable implements Runnable { 146 | private final Interpolator interpolator; 147 | private final int scrollToPosition; 148 | private final int scrollFromPosition; 149 | private final long duration; 150 | 151 | private long startTime = -1; 152 | private int currentPosition = -1; 153 | 154 | SmoothScrollRunnable(int fromPosition, int toPosition, long duration, Interpolator scrollAnimationInterpolator) { 155 | scrollFromPosition = fromPosition; 156 | scrollToPosition = toPosition; 157 | interpolator = scrollAnimationInterpolator; 158 | this.duration = duration; 159 | } 160 | 161 | @Override 162 | public void run() { 163 | if (startTime == -1) { 164 | startTime = System.currentTimeMillis(); 165 | } else { 166 | long normalizedTime = (1000 * (System.currentTimeMillis() - startTime)) / duration; 167 | normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); 168 | 169 | final int deltaY = Math.round((scrollFromPosition - scrollToPosition) 170 | * interpolator.getInterpolation(normalizedTime / 1000f)); 171 | currentPosition = scrollFromPosition - deltaY; 172 | 173 | moveOverScrollView(currentPosition); 174 | } 175 | 176 | if (scrollToPosition != currentPosition) { 177 | ViewCompat.postOnAnimation(OverScrollViewPager.this, this); 178 | } 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/java/agency/tango/materialintroscreen/widgets/SwipeableViewPager.java: -------------------------------------------------------------------------------- 1 | package agency.tango.materialintroscreen.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.CustomViewPager; 5 | import android.support.v4.view.MotionEventCompat; 6 | import android.util.AttributeSet; 7 | import android.view.KeyEvent; 8 | import android.view.MotionEvent; 9 | 10 | import agency.tango.materialintroscreen.adapter.SlidesAdapter; 11 | 12 | public class SwipeableViewPager extends CustomViewPager { 13 | private float startPos = 0; 14 | private int currentIt; 15 | private boolean swipingAllowed; 16 | private boolean alphaExitTransitionEnabled = false; 17 | 18 | public SwipeableViewPager(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | swipingAllowed = true; 21 | } 22 | 23 | @Override 24 | public boolean onInterceptTouchEvent(final MotionEvent event) { 25 | int action = MotionEventCompat.getActionMasked(event); 26 | 27 | switch (action) { 28 | case (MotionEvent.ACTION_DOWN): 29 | return super.onInterceptTouchEvent(event); 30 | case (MotionEvent.ACTION_MOVE): 31 | if (!swipingAllowed) { 32 | return false; 33 | } 34 | return super.onInterceptTouchEvent(event); 35 | case (MotionEvent.ACTION_UP): 36 | if (!swipingAllowed) { 37 | return false; 38 | } 39 | return super.onInterceptTouchEvent(event); 40 | default: 41 | return super.onInterceptTouchEvent(event); 42 | } 43 | } 44 | 45 | @Override 46 | public boolean onTouchEvent(final MotionEvent event) { 47 | int action = MotionEventCompat.getActionMasked(event); 48 | 49 | switch (action) { 50 | case (MotionEvent.ACTION_DOWN): 51 | startPos = event.getX(); 52 | currentIt = getCurrentItem(); 53 | resolveSwipingRightAllowed(); 54 | return super.onTouchEvent(event); 55 | case (MotionEvent.ACTION_MOVE): 56 | if (!swipingAllowed && startPos - event.getX() > 16) { 57 | return true; 58 | } 59 | return super.onTouchEvent(event); 60 | case (MotionEvent.ACTION_UP): 61 | if (!swipingAllowed && startPos - event.getX() > 16) { 62 | smoothScrollTo(getWidth() * currentIt, 0); 63 | return true; 64 | } 65 | startPos = 0; 66 | return super.onTouchEvent(event); 67 | default: 68 | return super.onTouchEvent(event); 69 | } 70 | } 71 | 72 | @Override 73 | public SlidesAdapter getAdapter() { 74 | return (SlidesAdapter) super.getAdapter(); 75 | } 76 | 77 | @Override 78 | public boolean executeKeyEvent(KeyEvent event) { 79 | return false; 80 | } 81 | 82 | public void moveToNextPage() 83 | { 84 | setCurrentItem(getCurrentItem() + 1, true); 85 | } 86 | 87 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 88 | super.onPageScrolled(position, positionOffset, positionOffsetPixels); 89 | } 90 | 91 | public int getPreviousItem() { 92 | return getCurrentItem() - 1; 93 | } 94 | 95 | public void setSwipingRightAllowed(boolean allowed) { 96 | swipingAllowed = allowed; 97 | } 98 | 99 | public void alphaExitTransitionEnabled(boolean alphaExitTransitionEnabled) { 100 | this.alphaExitTransitionEnabled = alphaExitTransitionEnabled; 101 | } 102 | 103 | public boolean alphaExitTransitionEnabled() { 104 | return alphaExitTransitionEnabled && swipingAllowed; 105 | } 106 | 107 | private void resolveSwipingRightAllowed() { 108 | if (getAdapter().shouldLockSlide(getCurrentItem())) { 109 | setSwipingRightAllowed(false); 110 | } else { 111 | setSwipingRightAllowed(true); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/anim/cycle_2.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/anim/shake_it.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable-v21/button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable/button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable/ic_finish.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable/ic_next.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable/ic_previous.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/drawable/ic_skip.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/layout-land/fragment_slide.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 28 | 29 | 35 | 36 | 44 | 45 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/layout/activity_material_intro.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 18 | 19 | 27 | 28 | 36 | 37 | 42 | 43 | 53 | 54 | 64 | 65 | 77 | 78 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/layout/empty_fragment_slide.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/layout/fragment_slide.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 31 | 32 | 40 | 41 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/transparent 4 | 5 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 72dp 4 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | material-intro-screen 3 | Grant permissions 4 | Please grant needed permissions 5 | Can\'t pass this slide. 6 | 7 | -------------------------------------------------------------------------------- /material-intro-screen/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':material-intro-screen' 2 | -------------------------------------------------------------------------------- /versions.gradle: -------------------------------------------------------------------------------- 1 | ext.androidSupport = "25.0.1" 2 | --------------------------------------------------------------------------------