├── .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 | [  ](https://bintray.com/tangoagency/maven/material-intro-screen/_latestVersion)
3 | [](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 | [](https://travis-ci.org/TangoAgency/material-intro-screen)
5 | [](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 | [](https://www.facebook.com/TangoDigitalAgency) [](https://twitter.com/Tango_Agency) [](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 |
--------------------------------------------------------------------------------