├── sample
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── 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
│ │ │ ├── drawable
│ │ │ │ ├── side_nav_bar.xml
│ │ │ │ ├── ic_menu.xml
│ │ │ │ └── ic_more_vert.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── drawables.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-v21
│ │ │ │ ├── ic_menu_send.xml
│ │ │ │ ├── ic_menu_slideshow.xml
│ │ │ │ ├── ic_menu_gallery.xml
│ │ │ │ ├── ic_menu_manage.xml
│ │ │ │ ├── ic_menu_camera.xml
│ │ │ │ └── ic_menu_share.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── menu
│ │ │ │ ├── main.xml
│ │ │ │ └── activity_main_drawer.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── nav_header_main.xml
│ │ │ │ ├── app_bar_main.xml
│ │ │ │ ├── activity_dialog_style.xml
│ │ │ │ └── content_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── uk
│ │ │ └── co
│ │ │ └── samuelwall
│ │ │ └── materialtaptargetprompt
│ │ │ └── sample
│ │ │ ├── DialogStyleActivity.java
│ │ │ └── MainActivity.java
│ └── test
│ │ └── java
│ │ └── uk
│ │ └── co
│ │ └── samuelwall
│ │ └── materialtaptargetprompt
│ │ └── sample
│ │ └── SampleUnitTest.java
├── proguard-rules.pro
└── build.gradle
├── library
├── .gitignore
├── proguard-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── attrs.xml
│ │ └── java
│ │ │ └── uk
│ │ │ └── co
│ │ │ └── samuelwall
│ │ │ └── materialtaptargetprompt
│ │ │ └── MaterialTapTargetPrompt.java
│ └── test
│ │ └── java
│ │ └── uk
│ │ └── co
│ │ └── samuelwall
│ │ └── materialtaptargetprompt
│ │ └── MaterialTapTargetPromptUnitTest.java
└── build.gradle
├── .idea
├── .name
├── copyright
│ ├── profiles_settings.xml
│ └── Material_Tap_Target_Prompt.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── compiler.xml
├── gradle.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .travis.yml
├── settings.gradle
├── CHANGELOG.md
├── gradle.properties
├── art
├── app_banner.svg
└── app_icon.svg
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | MaterialTapTargetPrompt
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | android:
3 | components:
4 | - tools
5 | - build-tools-23.0.3
6 | - android-23
7 | - extra
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angebagui/MaterialTapTargetPrompt/master/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/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 /home/sam/Android/Sdk/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 |
--------------------------------------------------------------------------------
/library/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 /home/sam/Android/Sdk/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | include ':library', ':sample'
18 |
19 | project(':library').name = 'material-tap-target-prompt'
20 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/copyright/Material_Tap_Target_Prompt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2016 Samuel Wall
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | #Mon Dec 28 10:00:20 PST 2015
18 | distributionBase=GRADLE_USER_HOME
19 | distributionPath=wrapper/dists
20 | zipStoreBase=GRADLE_USER_HOME
21 | zipStorePath=wrapper/dists
22 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
23 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## v1.1.4 (2016-08-21)
6 |
7 | ### Bug Fixes
8 |
9 | * Fixed prompt view position not changing on target view position change
10 | * Fixed null pointer exception in Builder.show when create returns null
11 |
12 | ## v1.1.3 (2016-06-26)
13 |
14 | ### Bug Fixes
15 |
16 | * Fixed crash if secondary text not set
17 | * Fixed incorrectly named method getTextSeparation, renamed to setTextSeparation
18 |
19 | ## v1.1.2 (2016-06-20)
20 |
21 | ### Bug Fixes
22 |
23 | * Fixed incorrect text positioning when view is clipped
24 |
25 | ## v1.1.1 (2016-06-13)
26 |
27 | ### Bug Fixes
28 |
29 | * Fixed incorrect margin use
30 |
31 | ## v1.1.0 (2016-06-12)
32 |
33 | ### Features
34 |
35 | * Added loading prompt theme from style
36 |
37 | ### Bug Fixes
38 |
39 | * Fixed prompt being drawn outside activity bounds
40 |
41 | ## v1.0.0 (2016-06-05)
42 |
43 | * Initial release
44 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/side_nav_bar.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #3F51B5
20 | #303F9F
21 | #FF4081
22 |
23 | #68EFAD
24 | #FFCC80
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_send.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/test/java/uk/co/samuelwall/materialtaptargetprompt/sample/SampleUnitTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package uk.co.samuelwall.materialtaptargetprompt.sample;
18 |
19 | import org.junit.Test;
20 |
21 | import static junit.framework.Assert.assertEquals;
22 |
23 | public class SampleUnitTest
24 | {
25 | @Test
26 | public void addition_isCorrect() throws Exception
27 | {
28 | assertEquals(4, 2 + 2);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
21 | 64dp
22 |
23 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_slideshow.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_gallery.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_more_vert.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_manage.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | 16dp
20 | 160dp
21 |
22 | 16dp
23 | 16dp
24 | 16dp
25 | 350dp
26 |
27 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_camera.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/drawables.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | @android:drawable/ic_menu_camera
19 | @android:drawable/ic_menu_gallery
20 | @android:drawable/ic_menu_slideshow
21 | @android:drawable/ic_menu_manage
22 | @android:drawable/ic_menu_share
23 | @android:drawable/ic_menu_send
24 |
25 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v21/ic_menu_share.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Material Tap Target Prompt Sample
19 |
20 | Open navigation drawer
21 | Close navigation drawer
22 |
23 | Settings
24 |
25 | Just how you want it
26 | Tap the menu icon to switch accounts, change settings & more
27 | Search your emails
28 | Tap the icon to quickly scan your emails
29 |
30 | Loaded from style
31 | This was loaded from a style
32 |
33 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2016 Samuel Wall
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | # Project-wide Gradle settings.
18 |
19 | # IDE (e.g. Android Studio) users:
20 | # Gradle settings configured through the IDE *will override*
21 | # any settings specified in this file.
22 |
23 | # For more details on how to configure your build environment visit
24 | # http://www.gradle.org/docs/current/userguide/build_environment.html
25 |
26 | # Specifies the JVM arguments used for the daemon process.
27 | # The setting is particularly useful for tweaking memory settings.
28 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
29 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
30 |
31 | # When configured, Gradle will run in incubating parallel mode.
32 | # This option should only be used with decoupled projects. More details, visit
33 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
34 | # org.gradle.parallel=true
35 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | android {
20 | compileSdkVersion 23
21 | buildToolsVersion "23.0.3"
22 |
23 | defaultConfig {
24 | applicationId "uk.co.samuelwall.materialtaptargetprompt.sample"
25 | minSdkVersion 7
26 | targetSdkVersion 23
27 | versionCode 6
28 | versionName "1.1.4"
29 | }
30 | buildTypes {
31 | release {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | lintOptions {
37 | abortOnError false
38 | }
39 | }
40 |
41 | dependencies {
42 | compile fileTree(dir: 'libs', include: ['*.jar'])
43 | testCompile 'junit:junit:4.12'
44 | compile 'com.android.support:appcompat-v7:23.4.0'
45 | compile 'com.android.support:support-v4:23.4.0'
46 | compile 'com.android.support:design:23.4.0'
47 | compile project(':material-tap-target-prompt')
48 | }
49 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
31 |
32 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/sample/src/main/java/uk/co/samuelwall/materialtaptargetprompt/sample/DialogStyleActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package uk.co.samuelwall.materialtaptargetprompt.sample;
18 |
19 | import android.os.Bundle;
20 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
21 | import android.support.v7.app.AppCompatActivity;
22 | import android.support.v7.widget.Toolbar;
23 | import android.view.View;
24 |
25 | import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
26 |
27 | public class DialogStyleActivity extends AppCompatActivity
28 | {
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState)
32 | {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_dialog_style);
35 |
36 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
37 | setSupportActionBar(toolbar);
38 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
39 | }
40 |
41 | public void showPrompt(View view)
42 | {
43 | new MaterialTapTargetPrompt.Builder(this)
44 | .setTarget(R.id.fab)
45 | .setAnimationInterpolator(new FastOutSlowInInterpolator())
46 | .setPrimaryText("Clipped to activity bounds")
47 | .setSecondaryText("The prompt does not draw outside the activity")
48 | .show();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
53 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
29 |
30 |
36 |
37 |
43 |
44 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/app_bar_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
25 |
26 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
45 |
46 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_dialog_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
29 |
30 |
34 |
35 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/art/app_banner.svg:
--------------------------------------------------------------------------------
1 |
2 |
85 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Material Tap Target Prompt
2 | [](https://travis-ci.org/sjwall/MaterialTapTargetPrompt)
3 | [](https://bintray.com/sjwall/maven/material-tap-target-prompt/_latestVersion)
4 |
5 | A Tap Target implementation in Android based on Material Design Onboarding guidelines. For more information on tap targets check out the [guidelines][1].
6 |
7 | 
8 |
9 | # Sample App
10 | The sample app in the repository is available on Google Play:
11 |
12 |
13 |
14 | # Gradle
15 | To use the gradle dependency, add this to build.gradle:
16 | ```groovy
17 | repositories {
18 | jcenter()
19 | }
20 |
21 | dependencies {
22 | compile 'uk.co.samuelwall:material-tap-target-prompt:1.1.4'
23 | }
24 | ```
25 |
26 | # Usage
27 | Basic usage is shown below with more examples in the sample app:
28 |
29 | ```java
30 | new MaterialTapTargetPrompt.Builder(MainActivity.this)
31 | .setTarget(findViewById(R.id.fab))
32 | .setPrimaryText("Send your first email")
33 | .setSecondaryText("Tap the envelop to start composing your first email")
34 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
35 | {
36 | @Override
37 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
38 | {
39 | //Do something such as storing a value so that this prompt is never shown again
40 | }
41 |
42 | @Override
43 | public void onHidePromptComplete()
44 | {
45 |
46 | }
47 | })
48 | .show();
49 | ```
50 |
51 | # License
52 | Copyright (C) 2016 Samuel Wall
53 |
54 | Licensed under the Apache License, Version 2.0 (the "License");
55 | you may not use this file except in compliance with the License.
56 | You may obtain a copy of the License at
57 |
58 | http://www.apache.org/licenses/LICENSE-2.0
59 |
60 | Unless required by applicable law or agreed to in writing, software
61 | distributed under the License is distributed on an "AS IS" BASIS,
62 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63 | See the License for the specific language governing permissions and
64 | limitations under the License.
65 |
66 |
67 |
68 |
69 | [1]: https://www.google.com/design/spec/growth-communications/feature-discovery.html#feature-discovery-design
70 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
32 |
33 |
45 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
30 |
31 |
40 |
41 |
50 |
51 |
60 |
61 |
70 |
71 |
80 |
81 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.library'
18 | apply plugin: 'com.github.dcendents.android-maven'
19 | apply plugin: 'com.jfrog.bintray'
20 |
21 | // Maven Group ID for the artifact
22 | group = 'uk.co.samuelwall'
23 | version = "1.1.4"
24 |
25 | android {
26 | compileSdkVersion 23
27 | buildToolsVersion "23.0.3"
28 |
29 | defaultConfig {
30 | minSdkVersion 7
31 | targetSdkVersion 23
32 | versionCode 6
33 | versionName "1.1.4"
34 | }
35 | buildTypes {
36 | debug {
37 | testCoverageEnabled true
38 | }
39 | release {
40 | minifyEnabled false
41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
42 | }
43 | }
44 | lintOptions {
45 | abortOnError false
46 | }
47 | }
48 |
49 | dependencies {
50 | compile fileTree(dir: 'libs', include: ['*.jar'])
51 | testCompile 'junit:junit:4.12'
52 | testCompile "org.robolectric:robolectric:3.1"
53 | compile 'com.android.support:support-annotations:23.4.0'
54 | }
55 |
56 | install {
57 | repositories.mavenInstaller {
58 | pom {
59 | //noinspection GroovyAssignabilityCheck
60 | project {
61 | packaging 'aar'
62 | name 'material-tap-target-prompt'
63 | url 'https://github.com/sjwall/MaterialTapTargetPrompt'
64 |
65 | groupId 'uk.co.samuelwall'
66 | artifactId 'material-tap-target-prompt'
67 |
68 | licenses {
69 | license {
70 | name 'The Apache Software License, Version 2.0'
71 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
72 | }
73 | }
74 | developers {
75 | developer {
76 | id 'sjwall'
77 | name 'Samuel Wall'
78 | email 'wallsamuelj@gmail.com'
79 | }
80 | }
81 | scm {
82 | connection 'https://github.com/sjwall/MaterialTapTargetPrompt.git'
83 | developerConnection 'https://github.com/sjwall/MaterialTapTargetPrompt.git'
84 | url 'https://github.com/sjwall/MaterialTapTargetPrompt'
85 |
86 | }
87 | }
88 | }
89 | }
90 | }
91 |
92 | task sourcesJar(type: Jar) {
93 | from android.sourceSets.main.java.srcDirs
94 | classifier = 'sources'
95 | }
96 |
97 | task javadoc(type: Javadoc) {
98 | source = android.sourceSets.main.java.srcDirs
99 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
100 | }
101 |
102 | task javadocJar(type: Jar, dependsOn: javadoc) {
103 | classifier = 'javadoc'
104 | from javadoc.destinationDir
105 | }
106 |
107 | artifacts {
108 | archives javadocJar
109 | archives sourcesJar
110 | }
111 |
112 | // Bintray
113 | bintray {
114 | Properties properties = new Properties()
115 | File propertiesFile = project.rootProject.file('local.properties');
116 | if (propertiesFile.exists()) {
117 | properties.load(propertiesFile.newDataInputStream())
118 | }
119 |
120 | user = properties.getProperty("bintray.user")
121 | key = properties.getProperty("bintray.apikey")
122 |
123 | configurations = ['archives']
124 |
125 | publish = false
126 | pkg {
127 | repo = 'maven'
128 | name = 'material-tap-target-prompt'
129 | desc = 'Material Design tap target for Android'
130 | websiteUrl = 'https://github.com/sjwall/MaterialTapTargetPrompt'
131 | vcsUrl = 'https://github.com/sjwall/MaterialTapTargetPrompt.git'
132 | licenses = ["Apache-2.0"]
133 | publicDownloadNumbers = false
134 | }
135 | }
136 |
137 | apply plugin: 'jacoco'
138 | //gradlew clean createDebugCoverageReport jacocoTestReport
139 | task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
140 |
141 | reports {
142 | xml.enabled = true
143 | html.enabled = true
144 | }
145 |
146 |
147 | def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
148 | def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
149 | def mainSrc = "${project.projectDir}/src/main/java"
150 |
151 | sourceDirectories = files([mainSrc])
152 | classDirectories = files([debugTree])
153 | executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")
154 | }
155 |
--------------------------------------------------------------------------------
/art/app_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
166 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sample/src/main/java/uk/co/samuelwall/materialtaptargetprompt/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package uk.co.samuelwall.materialtaptargetprompt.sample;
18 |
19 | import android.content.Intent;
20 | import android.os.Bundle;
21 | import android.support.design.widget.FloatingActionButton;
22 | import android.support.design.widget.Snackbar;
23 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 | import android.support.design.widget.NavigationView;
27 | import android.support.v4.view.GravityCompat;
28 | import android.support.v4.widget.DrawerLayout;
29 | import android.support.v7.app.ActionBarDrawerToggle;
30 | import android.support.v7.app.AppCompatActivity;
31 | import android.support.v7.widget.Toolbar;
32 | import android.view.Menu;
33 | import android.view.MenuItem;
34 |
35 | import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
36 |
37 | public class MainActivity extends AppCompatActivity
38 | implements NavigationView.OnNavigationItemSelectedListener
39 | {
40 |
41 | private MaterialTapTargetPrompt mFabPrompt;
42 |
43 | public void showFabPrompt(View view)
44 | {
45 | if (mFabPrompt != null)
46 | {
47 | return;
48 | }
49 | mFabPrompt = new MaterialTapTargetPrompt.Builder(MainActivity.this)
50 | .setTarget(findViewById(R.id.fab))
51 | .setPrimaryText("Send your first email")
52 | .setSecondaryText("Tap the envelop to start composing your first email")
53 | .setAnimationInterpolator(new FastOutSlowInInterpolator())
54 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
55 | {
56 | @Override
57 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
58 | {
59 | mFabPrompt = null;
60 | //Do something such as storing a value so that this prompt is never shown again
61 | }
62 |
63 | @Override
64 | public void onHidePromptComplete()
65 | {
66 |
67 | }
68 | })
69 | .create();
70 | mFabPrompt.show();
71 | }
72 |
73 | public void showSideNavigationPrompt(View view)
74 | {
75 | final MaterialTapTargetPrompt.Builder tapTargetPromptBuilder = new MaterialTapTargetPrompt.Builder(this)
76 | .setPrimaryText(R.string.menu_prompt_title)
77 | .setSecondaryText(R.string.menu_prompt_description)
78 | .setAnimationInterpolator(new FastOutSlowInInterpolator())
79 | .setMaxTextWidth(R.dimen.tap_target_menu_max_width)
80 | .setIcon(R.drawable.ic_menu);
81 | final Toolbar tb = (Toolbar) this.findViewById(R.id.toolbar);
82 | tapTargetPromptBuilder.setTarget(tb.getChildAt(1));
83 |
84 | tapTargetPromptBuilder.setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
85 | {
86 | @Override
87 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
88 | {
89 | //Do something such as storing a value so that this prompt is never shown again
90 | }
91 |
92 | @Override
93 | public void onHidePromptComplete()
94 | {
95 |
96 | }
97 | });
98 | tapTargetPromptBuilder.show();
99 | }
100 |
101 | public void showOverflowPrompt(View view)
102 | {
103 | final MaterialTapTargetPrompt.Builder tapTargetPromptBuilder = new MaterialTapTargetPrompt.Builder(this)
104 | .setPrimaryText(R.string.overflow_prompt_title)
105 | .setSecondaryText(R.string.overflow_prompt_description)
106 | .setAnimationInterpolator(new FastOutSlowInInterpolator())
107 | .setMaxTextWidth(R.dimen.tap_target_menu_max_width)
108 | .setIcon(R.drawable.ic_more_vert);
109 | final Toolbar tb = (Toolbar) this.findViewById(R.id.toolbar);
110 | tapTargetPromptBuilder.setTarget(tb.getChildAt(2));
111 |
112 | tapTargetPromptBuilder.setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
113 | {
114 | @Override
115 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
116 | {
117 | //Do something such as storing a value so that this prompt is never shown again
118 | }
119 |
120 | @Override
121 | public void onHidePromptComplete()
122 | {
123 |
124 | }
125 | });
126 | tapTargetPromptBuilder.show();
127 | }
128 |
129 | public void showStylePrompt(View view)
130 | {
131 | new MaterialTapTargetPrompt.Builder(this, R.style.MaterialTapTargetPromptTheme_FabTarget).show();
132 | }
133 |
134 | public void showDialog(View view)
135 | {
136 | startActivity(new Intent(this, DialogStyleActivity.class));
137 | }
138 |
139 | @Override
140 | protected void onCreate(Bundle savedInstanceState)
141 | {
142 | super.onCreate(savedInstanceState);
143 | setContentView(R.layout.activity_main);
144 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
145 | setSupportActionBar(toolbar);
146 |
147 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
148 | fab.setOnClickListener(new View.OnClickListener()
149 | {
150 | @Override
151 | public void onClick(View view)
152 | {
153 | if (mFabPrompt != null)
154 | {
155 | mFabPrompt.finish();
156 | mFabPrompt = null;
157 | }
158 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
159 | .setAction("Action", null).show();
160 | }
161 | });
162 |
163 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
164 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
165 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
166 | drawer.setDrawerListener(toggle);
167 | toggle.syncState();
168 |
169 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
170 | navigationView.setNavigationItemSelectedListener(this);
171 | }
172 |
173 | @Override
174 | public void onBackPressed()
175 | {
176 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
177 | if (drawer.isDrawerOpen(GravityCompat.START))
178 | {
179 | drawer.closeDrawer(GravityCompat.START);
180 | }
181 | else
182 | {
183 | super.onBackPressed();
184 | }
185 | }
186 |
187 | @Override
188 | public boolean onCreateOptionsMenu(Menu menu)
189 | {
190 | // Inflate the menu; this adds items to the action bar if it is present.
191 | getMenuInflater().inflate(R.menu.main, menu);
192 | return true;
193 | }
194 |
195 | @Override
196 | public boolean onOptionsItemSelected(MenuItem item)
197 | {
198 | // Handle action bar item clicks here. The action bar will
199 | // automatically handle clicks on the Home/Up button, so long
200 | // as you specify a parent activity in AndroidManifest.xml.
201 | int id = item.getItemId();
202 |
203 | //noinspection SimplifiableIfStatement
204 | if (id == R.id.action_settings)
205 | {
206 | return true;
207 | }
208 |
209 | return super.onOptionsItemSelected(item);
210 | }
211 |
212 | @SuppressWarnings("StatementWithEmptyBody")
213 | @Override
214 | public boolean onNavigationItemSelected(MenuItem item)
215 | {
216 | // Handle navigation view item clicks here.
217 | int id = item.getItemId();
218 |
219 | if (id == R.id.nav_camera)
220 | {
221 | // Handle the camera action
222 | }
223 | else if (id == R.id.nav_gallery)
224 | {
225 |
226 | }
227 | else if (id == R.id.nav_slideshow)
228 | {
229 |
230 | }
231 | else if (id == R.id.nav_manage)
232 | {
233 |
234 | }
235 | else if (id == R.id.nav_share)
236 | {
237 |
238 | }
239 | else if (id == R.id.nav_send)
240 | {
241 |
242 | }
243 |
244 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
245 | drawer.closeDrawer(GravityCompat.START);
246 | return true;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2016 Samuel Wall
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/library/src/test/java/uk/co/samuelwall/materialtaptargetprompt/MaterialTapTargetPromptUnitTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package uk.co.samuelwall.materialtaptargetprompt;
18 |
19 | import android.animation.ValueAnimator;
20 | import android.app.Activity;
21 | import android.graphics.Color;
22 | import android.graphics.Paint;
23 | import android.os.Build;
24 | import android.text.StaticLayout;
25 | import android.view.MotionEvent;
26 | import android.view.View;
27 | import android.view.ViewGroup;
28 | import android.view.animation.LinearInterpolator;
29 | import android.widget.FrameLayout;
30 |
31 | import org.junit.Before;
32 | import org.junit.Test;
33 | import org.junit.runner.RunWith;
34 | import org.robolectric.Robolectric;
35 | import org.robolectric.RobolectricGradleTestRunner;
36 | import org.robolectric.annotation.Config;
37 |
38 | import java.lang.reflect.Field;
39 | import java.lang.reflect.InvocationTargetException;
40 | import java.lang.reflect.Method;
41 |
42 | import static junit.framework.Assert.assertFalse;
43 | import static junit.framework.Assert.assertNotNull;
44 | import static junit.framework.Assert.assertTrue;
45 | import static org.junit.Assert.assertEquals;
46 | import static org.junit.Assert.assertNull;
47 |
48 | @RunWith(RobolectricGradleTestRunner.class)
49 | @Config(constants = uk.co.samuelwall.materialtaptargetprompt.BuildConfig.class, sdk = 22)
50 | public class MaterialTapTargetPromptUnitTest
51 | {
52 | Field mPromptView, mPromptViewPrimaryTextLayout, mPromptViewSecondaryTextLayout, mPromptViewPaintBackground, mPromptViewPaintFocal,
53 | mMaxTextWidth, mTextPadding, mBaseFocalRadius, mFocalRadius10Percent, mTargetView, mBaseLeft, mBaseTop, mAnimationCurrent,
54 | mRevealedAmount, mPaintPrimaryText, mPaintSecondaryText, mAnimationInterpolator, mTextSeparation, mFocalToTextPadding;
55 | Method mUpdateFocalCentrePosition, mGetParentView;
56 |
57 | @Before
58 | public void setup() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException
59 | {
60 | mPromptView = setFieldAccessible(MaterialTapTargetPrompt.class, "mView");
61 |
62 | Activity activity = createActivity();
63 | MaterialTapTargetPrompt dummyPrompt = new MaterialTapTargetPrompt.Builder(activity)
64 | .setTarget(0, 0)
65 | .setPrimaryText("")
66 | .show();
67 |
68 | mUpdateFocalCentrePosition = setMethodAccessible(MaterialTapTargetPrompt.class, "updateFocalCentrePosition");
69 | mGetParentView = setMethodAccessible(MaterialTapTargetPrompt.class, "getParentView");
70 |
71 | mMaxTextWidth = setFieldAccessible(MaterialTapTargetPrompt.class, "mMaxTextWidth");
72 | mTextPadding = setFieldAccessible(MaterialTapTargetPrompt.class, "mTextPadding");
73 | mBaseFocalRadius = setFieldAccessible(MaterialTapTargetPrompt.class, "mBaseFocalRadius");
74 | mFocalRadius10Percent = setFieldAccessible(MaterialTapTargetPrompt.class, "mFocalRadius10Percent");
75 | mTargetView = setFieldAccessible(MaterialTapTargetPrompt.class, "mTargetView");
76 | mBaseLeft = setFieldAccessible(MaterialTapTargetPrompt.class, "mBaseLeft");
77 | mBaseTop = setFieldAccessible(MaterialTapTargetPrompt.class, "mBaseTop");
78 | mAnimationCurrent = setFieldAccessible(MaterialTapTargetPrompt.class, "mAnimationCurrent");
79 | mRevealedAmount = setFieldAccessible(MaterialTapTargetPrompt.class, "mRevealedAmount");
80 | mPaintPrimaryText = setFieldAccessible(MaterialTapTargetPrompt.class, "mPaintPrimaryText");
81 | mPaintSecondaryText = setFieldAccessible(MaterialTapTargetPrompt.class, "mPaintSecondaryText");
82 | mAnimationInterpolator = setFieldAccessible(MaterialTapTargetPrompt.class, "mAnimationInterpolator");
83 | mFocalToTextPadding = setFieldAccessible(MaterialTapTargetPrompt.class, "mFocalToTextPadding");
84 |
85 | View view = (View) mPromptView.get(dummyPrompt);
86 | mPromptViewPrimaryTextLayout = setFieldAccessible(view.getClass(), "mPrimaryTextLayout");
87 | mPromptViewSecondaryTextLayout = setFieldAccessible(view.getClass(), "mSecondaryTextLayout");
88 | mPromptViewPaintBackground = setFieldAccessible(view.getClass(), "mPaintBackground");
89 | mPromptViewPaintFocal = setFieldAccessible(view.getClass(), "mPaintFocal");
90 | mTextSeparation = setFieldAccessible(view.getClass(), "mTextSeparation");
91 | }
92 |
93 | @Test
94 | public void promptFromVariables() throws IllegalAccessException, InvocationTargetException
95 | {
96 | LinearInterpolator interpolator = new LinearInterpolator();
97 | Activity activity = createActivity();
98 | MaterialTapTargetPrompt.Builder builder = new MaterialTapTargetPrompt.Builder(activity)
99 | .setTarget(50, 40)
100 | .setPrimaryText("Primary text")
101 | .setSecondaryText("Secondary text")
102 | .setMaxTextWidth(600f)
103 | .setTextPadding(50f)
104 | .setBackgroundColour(Color.BLUE)
105 | .setFocalColour(Color.GREEN)
106 | .setFocalRadius(55f)
107 | .setTextSeparation(22f)
108 | .setPrimaryTextSize(30f)
109 | .setSecondaryTextSize(20f)
110 | .setPrimaryTextColour(Color.CYAN)
111 | .setSecondaryTextColour(Color.GRAY)
112 | .setFocalToTextPadding(30f)
113 | .setAnimationInterpolator(interpolator);
114 |
115 | assertTrue(builder.isTargetSet());
116 | MaterialTapTargetPrompt prompt = builder.show();
117 |
118 | setScreenWidthAndHeight(prompt, 200, 600);
119 |
120 | assertEquals(600f, mMaxTextWidth.get(prompt));
121 | assertEquals(50f, mTextPadding.get(prompt));
122 | assertEquals(55f, mBaseFocalRadius.get(prompt));
123 | assertEquals(5.5f, mFocalRadius10Percent.get(prompt));
124 | assertNull(mTargetView.get(prompt));
125 | assertEquals(50f, mBaseLeft.get(prompt));
126 | assertEquals(40f, mBaseTop.get(prompt));
127 | assertEquals(30f, ((Paint) mPaintPrimaryText.get(prompt)).getTextSize(), 0f);
128 | assertEquals(20f, ((Paint) mPaintSecondaryText.get(prompt)).getTextSize(), 0f);
129 | assertEquals(Color.CYAN, ((Paint) mPaintPrimaryText.get(prompt)).getColor());
130 | assertEquals(Color.GRAY, ((Paint) mPaintSecondaryText.get(prompt)).getColor());
131 | assertEquals(interpolator, mAnimationInterpolator.get(prompt));
132 | assertEquals(30f, mFocalToTextPadding.get(prompt));
133 |
134 | View promptView = (View) mPromptView.get(prompt);
135 | assertEquals("Primary text", ((StaticLayout) mPromptViewPrimaryTextLayout.get(promptView)).getText());
136 | assertEquals(Color.BLUE, ((Paint) mPromptViewPaintBackground.get(promptView)).getColor());
137 | assertEquals(Color.GREEN, ((Paint) mPromptViewPaintFocal.get(promptView)).getColor());
138 | assertEquals(22f, mTextSeparation.get(promptView));
139 |
140 | prompt.dismiss();
141 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
142 | {
143 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
144 | }
145 | assertNull(promptView.getParent());
146 | }
147 |
148 | @Test
149 | public void promptNotCreatedWhenTargetNotSet()
150 | {
151 | Activity activity = createActivity();
152 | MaterialTapTargetPrompt.Builder builder = new MaterialTapTargetPrompt.Builder(activity)
153 | .setPrimaryText("Primary text")
154 | .setSecondaryText("Secondary text");
155 | assertNull(builder.create());
156 | }
157 |
158 | @Test
159 | public void promptNotCreatedWhenPrimaryTextNotSet()
160 | {
161 | Activity activity = createActivity();
162 | MaterialTapTargetPrompt.Builder builder = new MaterialTapTargetPrompt.Builder(activity)
163 | .setTarget(50, 40)
164 | .setSecondaryText("Secondary text");
165 | assertNull(builder.create());
166 | }
167 |
168 | @Test
169 | public void promptCreatedWhenSecondaryTextNotSet() throws IllegalAccessException
170 | {
171 | Activity activity = createActivity();
172 | MaterialTapTargetPrompt.Builder builder = new MaterialTapTargetPrompt.Builder(activity)
173 | .setTarget(50, 40)
174 | .setPrimaryText("Primary text");
175 | MaterialTapTargetPrompt prompt = builder.create();
176 | assertNotNull(prompt);
177 | prompt.show();
178 |
179 | View view = (View) mPromptView.get(prompt);
180 | assertNull(mPromptViewSecondaryTextLayout.get(view));
181 |
182 | prompt.finish();
183 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
184 | {
185 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
186 | }
187 | assertNull(view.getParent());
188 | }
189 |
190 | @Test
191 | public void promptAnimationCancel() throws IllegalAccessException
192 | {
193 | Activity activity = createActivity();
194 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
195 | .setTarget(10, 10)
196 | .setPrimaryText("Primary text")
197 | .show();
198 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
199 | {
200 | ((ValueAnimator) mAnimationCurrent.get(prompt)).cancel();
201 | }
202 | assertEquals(1f, mRevealedAmount.getFloat(prompt), 0f);
203 | assertNull(mAnimationCurrent.get(prompt));
204 |
205 | View promptView = (View) mPromptView.get(prompt);
206 | prompt.dismiss();
207 | assertNotNull(mAnimationCurrent.get(prompt));
208 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
209 | {
210 | ((ValueAnimator) mAnimationCurrent.get(prompt)).cancel();
211 | }
212 | assertNull(mAnimationCurrent.get(prompt));
213 | assertNull(promptView.getParent());
214 | }
215 |
216 | @Test
217 | public void promptCancelFinishAnimation() throws IllegalAccessException
218 | {
219 | Activity activity = createActivity();
220 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
221 | .setTarget(10, 10)
222 | .setPrimaryText("Primary text")
223 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
224 | {
225 | @Override
226 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
227 | {
228 |
229 | }
230 |
231 | @Override
232 | public void onHidePromptComplete()
233 | {
234 |
235 | }
236 | })
237 | .show();
238 |
239 | View promptView = (View) mPromptView.get(prompt);
240 | prompt.finish();
241 | assertNotNull(mAnimationCurrent.get(prompt));
242 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
243 | {
244 | ((ValueAnimator) mAnimationCurrent.get(prompt)).cancel();
245 | }
246 | assertNull(mAnimationCurrent.get(prompt));
247 | assertNull(promptView.getParent());
248 | }
249 |
250 | @Test
251 | public void promptTouchEventFocal() throws IllegalAccessException
252 | {
253 | Activity activity = createActivity();
254 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
255 | .setTarget(10, 10)
256 | .setPrimaryText("Primary text")
257 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
258 | {
259 | @Override
260 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
261 | {
262 | assertTrue(tappedTarget);
263 | }
264 |
265 | @Override
266 | public void onHidePromptComplete()
267 | {
268 |
269 | }
270 | })
271 | .show();
272 |
273 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
274 | {
275 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
276 | }
277 |
278 | View promptView = (View) mPromptView.get(prompt);
279 | assertFalse(promptView.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0)));
280 | }
281 |
282 | @Test
283 | public void promptTouchEventFocalCaptureEvent() throws IllegalAccessException
284 | {
285 | Activity activity = createActivity();
286 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
287 | .setTarget(10, 10)
288 | .setPrimaryText("Primary text")
289 | .setCaptureTouchEventOnFocal(true)
290 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
291 | {
292 | @Override
293 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
294 | {
295 | assertTrue(tappedTarget);
296 | }
297 |
298 | @Override
299 | public void onHidePromptComplete()
300 | {
301 |
302 | }
303 | })
304 | .show();
305 |
306 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
307 | {
308 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
309 | }
310 |
311 | View promptView = (View) mPromptView.get(prompt);
312 | assertTrue(promptView.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0)));
313 | }
314 |
315 | @Test
316 | public void promptTouchEventFocalNoListener() throws IllegalAccessException
317 | {
318 | Activity activity = createActivity();
319 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
320 | .setTarget(10, 10)
321 | .setPrimaryText("Primary text")
322 | .setCaptureTouchEventOnFocal(true)
323 | .show();
324 |
325 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
326 | {
327 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
328 | }
329 |
330 | View promptView = (View) mPromptView.get(prompt);
331 | assertTrue(promptView.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0)));
332 | }
333 |
334 | @Test
335 | public void promptTouchEventBackground() throws IllegalAccessException
336 | {
337 | Activity activity = createActivity();
338 | MaterialTapTargetPrompt prompt = new MaterialTapTargetPrompt.Builder(activity)
339 | .setTarget(10, 10)
340 | .setPrimaryText("Primary text")
341 | .setOnHidePromptListener(new MaterialTapTargetPrompt.OnHidePromptListener()
342 | {
343 | @Override
344 | public void onHidePrompt(MotionEvent event, boolean tappedTarget)
345 | {
346 | assertFalse(tappedTarget);
347 | }
348 |
349 | @Override
350 | public void onHidePromptComplete()
351 | {
352 |
353 | }
354 | })
355 | .show();
356 |
357 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
358 | {
359 | ((ValueAnimator) mAnimationCurrent.get(prompt)).end();
360 | }
361 |
362 | View promptView = (View) mPromptView.get(prompt);
363 | assertTrue(promptView.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 60, 60, 0)));
364 | }
365 |
366 | private Activity createActivity()
367 | {
368 | final Activity activity = Robolectric.buildActivity(Activity.class).create().get();
369 | activity.setContentView(new FrameLayout(activity));
370 | return activity;
371 | }
372 |
373 | private Field setFieldAccessible(final Class c, final String fieldName) throws NoSuchFieldException
374 | {
375 | final Field field = c.getDeclaredField(fieldName);
376 | field.setAccessible(true);
377 | return field;
378 | }
379 |
380 | private Method setMethodAccessible(final Class c, final String methodName) throws NoSuchMethodException
381 | {
382 | final Method method = c.getDeclaredMethod(methodName);
383 | method.setAccessible(true);
384 | return method;
385 | }
386 |
387 | private void setScreenWidthAndHeight(final MaterialTapTargetPrompt prompt, final int width, final int height) throws InvocationTargetException, IllegalAccessException
388 | {
389 | final ViewGroup parent = (ViewGroup) mGetParentView.invoke(prompt);
390 | //TODO make this work for all versions
391 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
392 | {
393 | parent.setLeft(0);
394 | parent.setRight(0);
395 | parent.setRight(width);
396 | parent.setBottom(height);
397 | }
398 | mUpdateFocalCentrePosition.invoke(prompt);
399 | }
400 | }
401 |
--------------------------------------------------------------------------------
/library/src/main/java/uk/co/samuelwall/materialtaptargetprompt/MaterialTapTargetPrompt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Samuel Wall
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package uk.co.samuelwall.materialtaptargetprompt;
18 |
19 | import android.animation.Animator;
20 | import android.animation.ValueAnimator;
21 | import android.annotation.TargetApi;
22 | import android.app.Activity;
23 | import android.content.Context;
24 | import android.content.res.TypedArray;
25 | import android.graphics.Canvas;
26 | import android.graphics.Color;
27 | import android.graphics.Paint;
28 | import android.graphics.PorterDuff;
29 | import android.graphics.drawable.Drawable;
30 | import android.os.Build;
31 | import android.support.annotation.ColorInt;
32 | import android.support.annotation.ColorRes;
33 | import android.support.annotation.DimenRes;
34 | import android.support.annotation.DrawableRes;
35 | import android.support.annotation.IdRes;
36 | import android.support.annotation.StringRes;
37 | import android.text.Layout;
38 | import android.text.StaticLayout;
39 | import android.text.TextPaint;
40 | import android.util.TypedValue;
41 | import android.view.MotionEvent;
42 | import android.view.View;
43 | import android.view.ViewGroup;
44 | import android.view.ViewTreeObserver;
45 | import android.view.animation.AccelerateDecelerateInterpolator;
46 | import android.view.animation.Interpolator;
47 |
48 | /**
49 | * A Material Design tap target onboarding implementation.
50 | *
51 | *
52 | *
Onboarding
53 | *
For more information about onboarding and tap targets, read the
54 | * Onboarding
55 | * Material Design guidelines.
56 | *
57 | */
58 | public class MaterialTapTargetPrompt
59 | {
60 | private Activity mActivity;
61 | private PromptView mView;
62 | private View mTargetView;
63 | private float mBaseLeft, mBaseTop;
64 | private float mBaseFocalRadius, mBaseBackgroundRadius;
65 | private float mFocalRadius10Percent;
66 | private float mRevealedAmount;
67 | private String mPrimaryText, mSecondaryText;
68 | private float mMaxTextWidth;
69 | private float mTextPadding;
70 | private boolean mTextPositionRight, mTextPositionAbove;
71 | private float mFocalToTextPadding;
72 | private int mPrimaryTextColourAlpha, mSecondaryTextColourAlpha;
73 | private ValueAnimator mAnimationCurrent, mAnimationFocalRipple;
74 | private Interpolator mAnimationInterpolator;
75 | private float mFocalRippleProgress;
76 | private int mBaseFocalRippleAlpha;
77 | private TextPaint mPaintPrimaryText, mPaintSecondaryText;
78 | private OnHidePromptListener mOnHidePromptListener;
79 | private boolean mDismissing;
80 | private ViewGroup mParentView;
81 | private boolean mParentViewIsDecor;
82 | private ViewGroup mClipToView;
83 | private final float mStatusBarHeight;
84 | private final ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
85 |
86 | MaterialTapTargetPrompt(final Activity activity)
87 | {
88 | mActivity = activity;
89 | mView = new PromptView(activity);
90 | mView.mOnPromptTouchedListener = new PromptView.OnPromptTouchedListener()
91 | {
92 | @Override
93 | public void onPromptTouched(MotionEvent event, boolean tappedTarget)
94 | {
95 | if (!mDismissing)
96 | {
97 | MaterialTapTargetPrompt.this.onHidePrompt(event, tappedTarget);
98 | if (tappedTarget)
99 | {
100 | finish();
101 | }
102 | else
103 | {
104 | dismiss();
105 | }
106 | }
107 | }
108 | };
109 |
110 | int resourceId = mView.getResources().getIdentifier("status_bar_height", "dimen", "android");
111 | if (resourceId > 0)
112 | {
113 | mStatusBarHeight = mView.getResources().getDimensionPixelSize(resourceId);
114 | }
115 | else
116 | {
117 | mStatusBarHeight = 0;
118 | }
119 |
120 | mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener()
121 | {
122 | @Override
123 | public void onGlobalLayout()
124 | {
125 | updateFocalCentrePosition();
126 | }
127 | };
128 | }
129 |
130 | /**
131 | * Returns {@link #mParentView}.
132 | *
133 | * If the {@link #mParentView} is {@link null} it determines what view it should be.
134 | *
135 | * @return The view to add the prompt view to.
136 | */
137 | private ViewGroup getParentView()
138 | {
139 | if (mParentView == null)
140 | {
141 | final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
142 | final ViewGroup contentView = (ViewGroup) ((ViewGroup) decorView.findViewById(android.R.id.content)).getChildAt(0);
143 | // If the content view is a drawer layout then that is the parent so
144 | // that the prompt can be added behind the navigation drawer
145 | if (contentView.getClass().getName().equals("android.support.v4.widget.DrawerLayout"))
146 | {
147 | mParentView = contentView;
148 | mParentViewIsDecor = false;
149 | }
150 | else
151 | {
152 | mParentView = decorView;
153 | mParentViewIsDecor = true;
154 | }
155 | mView.mClipBounds = mParentViewIsDecor;
156 | }
157 |
158 | return mParentView;
159 | }
160 |
161 | /**
162 | * Displays the prompt.
163 | */
164 | public void show()
165 | {
166 | final ViewGroup parent = getParentView();
167 | // If the content view is a drawer layout then that is the parent so
168 | // that the prompt can be added behind the navigation drawer
169 | if (parent.getClass().getName().equals("android.support.v4.widget.DrawerLayout"))
170 | {
171 | parent.addView(mView, 1);
172 | }
173 | else
174 | {
175 | parent.addView(mView);
176 | }
177 |
178 | addGlobalLayoutListener();
179 |
180 | updateFocalCentrePosition();
181 |
182 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
183 | {
184 | startRevealAnimation();
185 | }
186 | else
187 | {
188 | mView.mBackgroundRadius = mBaseBackgroundRadius;
189 | mView.mFocalRadius = mBaseFocalRadius;
190 | mView.mPaintFocal.setAlpha(255);
191 | mView.mPaintBackground.setAlpha(244);
192 | mPaintSecondaryText.setAlpha(mSecondaryTextColourAlpha);
193 | mPaintPrimaryText.setAlpha(mPrimaryTextColourAlpha);
194 | }
195 | }
196 |
197 | /**
198 | * Adds layout listener to view parent to capture layout changes.
199 | */
200 | private void addGlobalLayoutListener()
201 | {
202 | final ViewTreeObserver viewTreeObserver = getParentView().getViewTreeObserver();
203 | if (viewTreeObserver.isAlive())
204 | {
205 | viewTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
206 | }
207 | }
208 |
209 | /**
210 | * Removes global layout listener added in {@link #addGlobalLayoutListener()}.
211 | */
212 | private void removeGlobalLayoutListener()
213 | {
214 | final ViewTreeObserver viewTreeObserver = getParentView().getViewTreeObserver();
215 | if (viewTreeObserver.isAlive())
216 | {
217 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
218 | {
219 | viewTreeObserver.removeOnGlobalLayoutListener(mGlobalLayoutListener);
220 | }
221 | else
222 | {
223 | //noinspection deprecation
224 | viewTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
225 | }
226 | }
227 | }
228 |
229 | /**
230 | * Removes the prompt from view, using a expand and fade animation.
231 | *
232 | * This is treated as if the user has touched the target focal point.
233 | */
234 | public void finish()
235 | {
236 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
237 | {
238 | if (mDismissing)
239 | {
240 | return;
241 | }
242 | mDismissing = true;
243 | if (mAnimationCurrent != null)
244 | {
245 | mAnimationCurrent.removeAllListeners();
246 | mAnimationCurrent.cancel();
247 | mAnimationCurrent = null;
248 | }
249 | mAnimationCurrent = ValueAnimator.ofFloat(1f, 0f);
250 | mAnimationCurrent.setDuration(225);
251 | mAnimationCurrent.setInterpolator(mAnimationInterpolator);
252 | mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
253 | {
254 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
255 | @Override
256 | public void onAnimationUpdate(ValueAnimator animation)
257 | {
258 | final float value = (float) animation.getAnimatedValue();
259 | mRevealedAmount = 1f + ((1f - value) / 4);
260 | mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
261 | mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
262 | mView.mPaintFocal.setAlpha((int) (255 * value));
263 | mView.mPaintBackground.setAlpha((int) (244 * value));
264 | mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * value));
265 | mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * value));
266 | if (mView.mIconDrawable != null)
267 | {
268 | mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
269 | }
270 | mView.invalidate();
271 | }
272 | });
273 | mAnimationCurrent.addListener(new AnimatorListener()
274 | {
275 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
276 | @Override
277 | public void onAnimationEnd(Animator animation)
278 | {
279 | removeGlobalLayoutListener();
280 | getParentView().removeView(mView);
281 | mAnimationCurrent.removeAllListeners();
282 | mAnimationCurrent = null;
283 | mDismissing = false;
284 | onHidePromptComplete();
285 | mParentView = null;
286 | }
287 |
288 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
289 | @Override
290 | public void onAnimationCancel(Animator animation)
291 | {
292 | removeGlobalLayoutListener();
293 | getParentView().removeView(mView);
294 | mAnimationCurrent.removeAllListeners();
295 | mAnimationCurrent = null;
296 | mDismissing = false;
297 | onHidePromptComplete();
298 | mParentView = null;
299 | }
300 | });
301 | mAnimationCurrent.start();
302 | }
303 | else
304 | {
305 | removeGlobalLayoutListener();
306 | getParentView().removeView(mView);
307 | onHidePromptComplete();
308 | mParentView = null;
309 | }
310 | }
311 |
312 | /**
313 | * Removes the prompt from view, using a contract and fade animation.
314 | *
315 | * This is treated as if the user has touched outside the target focal point.
316 | */
317 | public void dismiss()
318 | {
319 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
320 | {
321 | if (mDismissing)
322 | {
323 | return;
324 | }
325 | mDismissing = true;
326 | if (mAnimationCurrent != null)
327 | {
328 | mAnimationCurrent.removeAllListeners();
329 | mAnimationCurrent.cancel();
330 | mAnimationCurrent = null;
331 | }
332 | mAnimationCurrent = ValueAnimator.ofFloat(1f, 0f);
333 | mAnimationCurrent.setDuration(225);
334 | mAnimationCurrent.setInterpolator(mAnimationInterpolator);
335 | mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
336 | {
337 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
338 | @Override
339 | public void onAnimationUpdate(ValueAnimator animation)
340 | {
341 | mRevealedAmount = (float) animation.getAnimatedValue();
342 | mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
343 | mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
344 | mView.mPaintBackground.setAlpha((int) (244 * mRevealedAmount));
345 | mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * mRevealedAmount));
346 | mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * mRevealedAmount));
347 | if (mView.mIconDrawable != null)
348 | {
349 | mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
350 | }
351 | mView.invalidate();
352 | }
353 | });
354 | mAnimationCurrent.addListener(new AnimatorListener()
355 | {
356 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
357 | @Override
358 | public void onAnimationEnd(Animator animation)
359 | {
360 | removeGlobalLayoutListener();
361 | getParentView().removeView(mView);
362 | mAnimationCurrent.removeAllListeners();
363 | mAnimationCurrent = null;
364 | mDismissing = false;
365 | onHidePromptComplete();
366 | mParentView = null;
367 | }
368 |
369 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
370 | @Override
371 | public void onAnimationCancel(Animator animation)
372 | {
373 | removeGlobalLayoutListener();
374 | getParentView().removeView(mView);
375 | mAnimationCurrent.removeAllListeners();
376 | mAnimationCurrent = null;
377 | mDismissing = false;
378 | onHidePromptComplete();
379 | mParentView = null;
380 | }
381 | });
382 | mAnimationCurrent.start();
383 | }
384 | else
385 | {
386 | removeGlobalLayoutListener();
387 | getParentView().removeView(mView);
388 | onHidePromptComplete();
389 | mParentView = null;
390 | }
391 | }
392 |
393 | @TargetApi(11)
394 | private void startRevealAnimation()
395 | {
396 | mPaintSecondaryText.setAlpha(0);
397 | mPaintPrimaryText.setAlpha(0);
398 | mView.mPaintBackground.setAlpha(0);
399 | mView.mPaintFocal.setAlpha(0);
400 | mView.mFocalRadius = 0;
401 | mView.mBackgroundRadius = 0;
402 | if (mView.mIconDrawable != null)
403 | {
404 | mView.mIconDrawable.setAlpha(0);
405 | }
406 | mRevealedAmount = 0f;
407 | mAnimationCurrent = ValueAnimator.ofFloat(0f, 1f);
408 | mAnimationCurrent.setInterpolator(mAnimationInterpolator);
409 | mAnimationCurrent.setDuration(225);
410 | mAnimationCurrent.setStartDelay(500);
411 | mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
412 | {
413 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
414 | @Override
415 | public void onAnimationUpdate(ValueAnimator animation)
416 | {
417 | mRevealedAmount = (float) animation.getAnimatedValue();
418 | mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
419 | mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
420 | mView.mPaintFocal.setAlpha((int) (255 * mRevealedAmount));
421 | mView.mPaintBackground.setAlpha((int) (244 * mRevealedAmount));
422 | mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * mRevealedAmount));
423 | mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * mRevealedAmount));
424 | if (mView.mIconDrawable != null)
425 | {
426 | mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
427 | }
428 | mView.invalidate();
429 | }
430 | });
431 | mAnimationCurrent.addListener(new AnimatorListener()
432 | {
433 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
434 | @Override
435 | public void onAnimationEnd(Animator animation)
436 | {
437 | animation.removeAllListeners();
438 | mAnimationCurrent = null;
439 | mRevealedAmount = 1;
440 | startIdleAnimations();
441 | }
442 |
443 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
444 | @Override
445 | public void onAnimationCancel(Animator animation)
446 | {
447 | animation.removeAllListeners();
448 | mRevealedAmount = 1;
449 | mAnimationCurrent = null;
450 | }
451 | });
452 | mAnimationCurrent.start();
453 | }
454 |
455 | @TargetApi(11)
456 | private void startIdleAnimations()
457 | {
458 | if (mAnimationCurrent != null)
459 | {
460 | mAnimationCurrent.removeAllUpdateListeners();
461 | mAnimationCurrent.cancel();
462 | mAnimationCurrent = null;
463 | }
464 | mAnimationCurrent = ValueAnimator.ofFloat(0, mFocalRadius10Percent, 0);
465 | mAnimationCurrent.setInterpolator(mAnimationInterpolator);
466 | mAnimationCurrent.setDuration(1000);
467 | mAnimationCurrent.setStartDelay(225);
468 | mAnimationCurrent.setRepeatCount(ValueAnimator.INFINITE);
469 | mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
470 | {
471 | boolean direction = true;
472 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
473 | @Override
474 | public void onAnimationUpdate(ValueAnimator animation)
475 | {
476 | final float newFocalFraction = (Float) animation.getAnimatedValue();
477 | boolean newDirection = direction;
478 | if (newFocalFraction < mFocalRippleProgress && direction)
479 | {
480 | newDirection = false;
481 | }
482 | else if (newFocalFraction > mFocalRippleProgress && !direction)
483 | {
484 | newDirection = true;
485 | }
486 | if (newDirection != direction && !newDirection)
487 | {
488 | mAnimationFocalRipple.start();
489 | }
490 | direction = newDirection;
491 | mFocalRippleProgress = newFocalFraction;
492 | mView.mFocalRadius = mBaseFocalRadius + mFocalRippleProgress;
493 | mView.invalidate();
494 | }
495 | });
496 | mAnimationCurrent.start();
497 | if (mAnimationFocalRipple != null)
498 | {
499 | mAnimationFocalRipple.removeAllUpdateListeners();
500 | mAnimationFocalRipple.cancel();
501 | mAnimationFocalRipple = null;
502 | }
503 | final float baseRadius = mBaseFocalRadius + mFocalRadius10Percent;
504 | mAnimationFocalRipple = ValueAnimator.ofFloat(baseRadius, baseRadius + (mFocalRadius10Percent * 6));
505 | mAnimationFocalRipple.setInterpolator(mAnimationInterpolator);
506 | mAnimationFocalRipple.setDuration(500);
507 | mAnimationFocalRipple.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
508 | {
509 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
510 | @Override
511 | public void onAnimationUpdate(ValueAnimator animation)
512 | {
513 | mView.mFocalRippleSize = (float) animation.getAnimatedValue();
514 | final float fraction;
515 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
516 | {
517 | fraction = animation.getAnimatedFraction();
518 | }
519 | else
520 | {
521 | fraction = (mFocalRadius10Percent * 6) / (mView.mFocalRippleSize - mBaseFocalRadius - mFocalRadius10Percent);
522 | }
523 | mView.mFocalRippleAlpha = (int) (mBaseFocalRippleAlpha * (1f - fraction));
524 | }
525 | });
526 | }
527 |
528 | private void updateFocalCentrePosition()
529 | {
530 | updateClipBounds();
531 | if (mTargetView != null)
532 | {
533 | final int[] viewPosition = new int[2];
534 | mView.getLocationInWindow(viewPosition);
535 | final int[] targetPosition = new int[2];
536 | mTargetView.getLocationInWindow(targetPosition);
537 |
538 | mView.mCentreLeft = mBaseLeft + targetPosition[0] - viewPosition[0] + (mTargetView.getWidth() / 2);
539 | mView.mCentreTop = mBaseTop + targetPosition[1] - viewPosition[1] + (mTargetView.getHeight() / 2);
540 | }
541 | else
542 | {
543 | mView.mCentreLeft = mBaseLeft;
544 | mView.mCentreTop = mBaseTop;
545 | }
546 |
547 | final ViewGroup parent = getParentView();
548 | mTextPositionAbove = mView.mCentreTop > parent.getHeight() / 2;
549 | mTextPositionRight = mView.mCentreLeft > parent.getWidth() / 2;
550 |
551 | updateTextPositioning();
552 | }
553 |
554 | private void updateTextPositioning()
555 | {
556 | final float primaryTextWidth = mPaintPrimaryText.measureText(mPrimaryText);
557 | final float secondaryTextWidth = mSecondaryText != null ? mPaintSecondaryText.measureText(mSecondaryText) : 0;
558 | final float textWidth;
559 | final float maxWidth = Math.max(80, (mView.mClipBounds ? mView.mClipBoundsRight - mView.mClipBoundsLeft : getParentView().getWidth()) - (mTextPadding * 2));
560 | final float textWidthCalculation = Math.min(mMaxTextWidth, Math.max(primaryTextWidth, secondaryTextWidth));
561 | if (textWidthCalculation > maxWidth)
562 | {
563 | mView.mTextLeft = (mView.mClipBounds ? mView.mClipBoundsLeft : 0) + mTextPadding;
564 | textWidth = maxWidth;
565 | }
566 | else
567 | {
568 | if (mTextPositionRight)
569 | {
570 | mView.mTextLeft = (mView.mClipBounds ? mView.mClipBoundsRight : getParentView().getRight()) - mTextPadding - textWidthCalculation;
571 | }
572 | else
573 | {
574 | mView.mTextLeft = mTextPadding + (mView.mClipBounds ? mView.mClipBoundsLeft : 0);
575 | }
576 | textWidth = textWidthCalculation;
577 | }
578 |
579 | mView.mPrimaryTextLayout = new StaticLayout(mPrimaryText, mPaintPrimaryText, (int) textWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
580 |
581 | mView.mPrimaryTextTop = mView.mCentreTop;
582 | if (mTextPositionAbove)
583 | {
584 | mView.mPrimaryTextTop = mView.mPrimaryTextTop - mBaseFocalRadius - mFocalToTextPadding - mView.mPrimaryTextLayout.getHeight();
585 | }
586 | else
587 | {
588 | mView.mPrimaryTextTop += mBaseFocalRadius + mFocalToTextPadding;
589 | }
590 |
591 | if (mSecondaryText != null)
592 | {
593 | mView.mSecondaryTextLayout = new StaticLayout(mSecondaryText, mPaintSecondaryText, (int) textWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
594 | if (mTextPositionAbove)
595 | {
596 | mView.mPrimaryTextTop = mView.mPrimaryTextTop - mView.mTextSeparation - mView.mSecondaryTextLayout.getHeight();
597 | }
598 |
599 | mView.mSecondaryTextOffsetTop = mView.mPrimaryTextLayout.getHeight() + mView.mTextSeparation;
600 | }
601 | else
602 | {
603 | mView.mSecondaryTextLayout = null;
604 | }
605 |
606 | updateBackgroundRadius();
607 | updateIconPosition();
608 | }
609 |
610 | private void updateBackgroundRadius()
611 | {
612 | final float height;
613 | if (mTextPositionAbove)
614 | {
615 | height = mView.mCentreTop - mView.mPrimaryTextTop;
616 | }
617 | else
618 | {
619 | height = mView.mPrimaryTextTop + mView.mPrimaryTextLayout.getHeight() + (mView.mSecondaryTextLayout != null ? mView.mSecondaryTextLayout.getHeight() : 0) - mView.mCentreTop + mView.mTextSeparation;
620 | }
621 |
622 | final float length;
623 | if (mTextPositionRight)
624 | {
625 | length = mView.mCentreLeft - mView.mTextLeft + mTextPadding;
626 | }
627 | else
628 | {
629 | length = mView.mTextLeft + Math.max(mView.mPrimaryTextLayout.getWidth(), mView.mSecondaryTextLayout != null ? mView.mSecondaryTextLayout.getWidth() : 0)
630 | + mTextPadding - mView.mCentreLeft;
631 | }
632 | //noinspection SuspiciousNameCombination
633 | mBaseBackgroundRadius = Double.valueOf(Math.sqrt(Math.pow(length, 2) + Math.pow(height, 2))).floatValue();
634 | }
635 |
636 | private void updateIconPosition()
637 | {
638 | if (mView.mIconDrawable != null)
639 | {
640 | mView.mIconDrawableLeft = mView.mCentreLeft - (mView.mIconDrawable.getIntrinsicWidth() / 2);
641 | mView.mIconDrawableTop = mView.mCentreTop - (mView.mIconDrawable.getIntrinsicHeight() / 2);
642 | }
643 | else if (mView.mTargetView != null)
644 | {
645 | mView.mIconDrawableLeft = mView.mCentreLeft - (mView.mTargetView.getWidth() / 2);
646 | mView.mIconDrawableTop = mView.mCentreTop - (mView.mTargetView.getHeight() / 2);
647 | }
648 | }
649 |
650 | private void updateClipBounds()
651 | {
652 | if (mClipToView != null)
653 | {
654 | mView.mClipBounds = true;
655 | mView.mClipBoundsLeft = mClipToView.getLeft();
656 | mView.mClipBoundsBottom = mClipToView.getBottom();
657 | mView.mClipBoundsTop = mClipToView.getTop();
658 | mView.mClipBoundsRight = mClipToView.getRight();
659 | if (mParentViewIsDecor)
660 | {
661 | mView.mClipBoundsTop += mStatusBarHeight;
662 | mView.mClipBoundsBottom += mStatusBarHeight;
663 | }
664 | }
665 | else if (mParentViewIsDecor)
666 | {
667 | mView.mClipBounds = true;
668 | //Stop the canvas drawing over the status bar
669 | mView.mClipBoundsTop = mStatusBarHeight;
670 | mView.mClipBoundsLeft = 0f;
671 | mView.mClipBoundsBottom = mActivity.getResources().getDisplayMetrics().heightPixels - mStatusBarHeight;
672 | mView.mClipBoundsRight = mActivity.getResources().getDisplayMetrics().widthPixels;
673 | }
674 | else
675 | {
676 | mView.mClipBounds = false;
677 | }
678 | }
679 |
680 | protected void onHidePrompt(final MotionEvent event, final boolean targetTapped)
681 | {
682 | if (mOnHidePromptListener != null)
683 | {
684 | mOnHidePromptListener.onHidePrompt(event, targetTapped);
685 | }
686 | }
687 |
688 | protected void onHidePromptComplete()
689 | {
690 | if (mOnHidePromptListener != null)
691 | {
692 | mOnHidePromptListener.onHidePromptComplete();
693 | }
694 | }
695 |
696 | /**
697 | * View used to render the tap target.
698 | */
699 | static class PromptView extends View
700 | {
701 | private float mCentreLeft, mCentreTop;
702 | private Paint mPaintBackground, mPaintFocal;
703 | private float mFocalRadius, mBackgroundRadius;
704 | private float mFocalRippleSize;
705 | private int mFocalRippleAlpha;
706 | private Drawable mIconDrawable;
707 | private float mIconDrawableLeft;
708 | private float mIconDrawableTop;
709 | private float mTextLeft;
710 | private float mPrimaryTextTop;
711 | private float mSecondaryTextOffsetTop;
712 | private Layout mPrimaryTextLayout;
713 | private Layout mSecondaryTextLayout;
714 | private boolean mDrawRipple = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
715 | private OnPromptTouchedListener mOnPromptTouchedListener;
716 | private boolean mCaptureTouchEventOnFocal;
717 | private float mClipBoundsTop, mClipBoundsLeft, mClipBoundsBottom, mClipBoundsRight;
718 | private View mTargetView;
719 | private float mTextSeparation;
720 | private boolean mClipBounds;
721 |
722 | public PromptView(final Context context)
723 | {
724 | super(context);
725 | }
726 |
727 | @Override
728 | public void onDraw(final Canvas canvas)
729 | {
730 | if (mClipBounds)
731 | {
732 | canvas.clipRect(mClipBoundsLeft, mClipBoundsTop, mClipBoundsRight, mClipBoundsBottom);
733 | }
734 |
735 | //Draw the backgrounds
736 | canvas.drawCircle(mCentreLeft, mCentreTop, mBackgroundRadius, mPaintBackground);
737 | //Draw the ripple
738 | if (mDrawRipple)
739 | {
740 | final int oldAlpha = mPaintFocal.getAlpha();
741 | mPaintFocal.setAlpha(mFocalRippleAlpha);
742 | canvas.drawCircle(mCentreLeft, mCentreTop, mFocalRippleSize, mPaintFocal);
743 | mPaintFocal.setAlpha(oldAlpha);
744 | }
745 | //Draw the focal
746 | canvas.drawCircle(mCentreLeft, mCentreTop, mFocalRadius, mPaintFocal);
747 |
748 | //Draw the icon
749 | if (mIconDrawable != null)
750 | {
751 | canvas.translate(mIconDrawableLeft, mIconDrawableTop);
752 | mIconDrawable.draw(canvas);
753 | canvas.translate(-mIconDrawableLeft, -mIconDrawableTop);
754 | }
755 | else if (mTargetView != null)
756 | {
757 | canvas.translate(mIconDrawableLeft, mIconDrawableTop);
758 | mTargetView.draw(canvas);
759 | canvas.translate(-mIconDrawableLeft, -mIconDrawableTop);
760 | }
761 |
762 | //Draw the text
763 | canvas.translate(mTextLeft, mPrimaryTextTop);
764 | mPrimaryTextLayout.draw(canvas);
765 | if (mSecondaryTextLayout != null)
766 | {
767 | canvas.translate(0f, mSecondaryTextOffsetTop);
768 | mSecondaryTextLayout.draw(canvas);
769 | }
770 | }
771 |
772 | @Override
773 | public boolean onTouchEvent(MotionEvent event)
774 | {
775 | final float x = event.getX();
776 | final float y = event.getY();
777 | boolean captureEvent = pointInCircle(x, y, mBackgroundRadius);
778 | if (captureEvent && pointInCircle(x, y, mFocalRadius))
779 | {
780 | captureEvent = mCaptureTouchEventOnFocal;
781 | onPromptTouched(event, true);
782 | }
783 | else
784 | {
785 | onPromptTouched(event, false);
786 | }
787 | return captureEvent;
788 | }
789 |
790 | /**
791 | * Determines if a point is in the centre of a circle with a radius from the point ({@link #mCentreLeft, {@link #mCentreTop}}.
792 | *
793 | * @param x The x position in the view.
794 | * @param y The y position in the view.
795 | * @param radius The radius of the circle.
796 | * @return True if the point (x, y) is in the circle.
797 | */
798 | private boolean pointInCircle(final float x, final float y, final float radius)
799 | {
800 | return Math.pow(x - mCentreLeft, 2) + Math.pow(y - mCentreTop, 2) < Math.pow(radius, 2);
801 | }
802 |
803 | protected void onPromptTouched(final MotionEvent event, final boolean targetTapped)
804 | {
805 | if (mOnPromptTouchedListener != null)
806 | {
807 | mOnPromptTouchedListener.onPromptTouched(event, targetTapped);
808 | }
809 | }
810 |
811 | /**
812 | * Interface definition for a callback to be invoked when a {@link PromptView} is touched.
813 | */
814 | public interface OnPromptTouchedListener
815 | {
816 | /**
817 | * Called when a touch event occurs in the prompt view.
818 | *
819 | * @param event The touch event that triggered the dismiss or finish.
820 | * @param tappedTarget True if the prompt focal point was touched.
821 | */
822 | void onPromptTouched(final MotionEvent event, final boolean tappedTarget);
823 | }
824 | }
825 |
826 | /**
827 | * A builder to create a {@link MaterialTapTargetPrompt} instance.
828 | */
829 | public static class Builder
830 | {
831 | /**
832 | * The containing activity.
833 | */
834 | private Activity mActivity;
835 |
836 | private boolean mTargetSet;
837 |
838 | /**
839 | * The view to place the prompt around.
840 | */
841 | private View mTargetView;
842 |
843 | /**
844 | * The left and top positioning for the focal centre point.
845 | */
846 | private float mCentreLeft, mCentreTop;
847 |
848 | /**
849 | * The text to display.
850 | */
851 | private String mPrimaryText, mSecondaryText;
852 | private int mPrimaryTextColour, mSecondaryTextColour, mBackgroundColour, mFocalColour;
853 | private float mFocalRadius;
854 | private float mPrimaryTextSize, mSecondaryTextSize;
855 | private float mMaxTextWidth;
856 | private float mTextPadding;
857 | private float mFocalToTextPadding;
858 | private Interpolator mAnimationInterpolator;
859 | private Drawable mIconDrawable;
860 | private OnHidePromptListener mOnHidePromptListener;
861 | private boolean mCaptureTouchEventOnFocal;
862 | private float mTextSeparation;
863 |
864 | /**
865 | * Creates a builder for a tap target prompt that uses the default
866 | * tap target prompt theme.
867 | *
868 | * @param activity the activity to show the prompt within.
869 | */
870 | public Builder(final Activity activity)
871 | {
872 | this(activity, 0);
873 | }
874 |
875 | /**
876 | * Creates a builder for a material tap target prompt that uses an explicit theme
877 | * resource.
878 | *
879 | * The {@code themeResId} may be specified as {@code 0}
880 | * to use the parent {@code context}'s resolved value for
881 | * {@link R.attr#MaterialTapTargetPromptTheme}.
882 | *
883 | * @param activity the activity to show the prompt within.
884 | * @param themeResId the resource ID of the theme against which to inflate
885 | * this dialog, or {@code 0} to use the parent
886 | * {@code context}'s default material tap target prompt theme
887 | */
888 | public Builder(final Activity activity, int themeResId)
889 | {
890 | mActivity = activity;
891 | //Attempt to load the theme from the activity theme
892 | if (themeResId == 0)
893 | {
894 | final TypedValue outValue = new TypedValue();
895 | activity.getTheme().resolveAttribute(R.attr.MaterialTapTargetPromptTheme, outValue, true);
896 | themeResId = outValue.resourceId;
897 | }
898 |
899 | final float density = activity.getResources().getDisplayMetrics().density;
900 | final TypedArray a = mActivity.obtainStyledAttributes(themeResId, R.styleable.PromptView);
901 | mPrimaryTextColour = a.getColor(R.styleable.PromptView_primaryTextColour, Color.WHITE);
902 | mSecondaryTextColour = a.getColor(R.styleable.PromptView_secondaryTextColour, Color.argb(179, 255, 255, 255));
903 | mPrimaryText = a.getString(R.styleable.PromptView_primaryText);
904 | mSecondaryText = a.getString(R.styleable.PromptView_secondaryText);
905 | mBackgroundColour = a.getColor(R.styleable.PromptView_backgroundColour, Color.argb(244, 63, 81, 181));
906 | mFocalColour = a.getColor(R.styleable.PromptView_focalColour, Color.WHITE);
907 | mFocalRadius = a.getDimension(R.styleable.PromptView_focalRadius, density * 44);
908 | mPrimaryTextSize = a.getDimension(R.styleable.PromptView_primaryTextSize, 22 * density);
909 | mSecondaryTextSize = a.getDimension(R.styleable.PromptView_secondaryTextSize, 18 * density);
910 | mMaxTextWidth = a.getDimension(R.styleable.PromptView_maxTextWidth, 400 * density);
911 | mTextPadding = a.getDimension(R.styleable.PromptView_textPadding, 40 * density);
912 | mFocalToTextPadding = a.getDimension(R.styleable.PromptView_focalToTextPadding, 20 * density);
913 | mTextSeparation = a.getDimension(R.styleable.PromptView_textSeparation, 16 * density);
914 | final int targetId = a.getResourceId(R.styleable.PromptView_target, 0);
915 | a.recycle();
916 |
917 | if (targetId != 0)
918 | {
919 | mTargetView = mActivity.findViewById(targetId);
920 | if (mTargetView != null)
921 | {
922 | mTargetSet = true;
923 | }
924 | }
925 | }
926 |
927 | /**
928 | * Set the view for the prompt to focus on.
929 | * @param target The view that the prompt will highlight.
930 | * @return This Builder object to allow for chaining of calls to set methods
931 | */
932 | public Builder setTarget(final View target)
933 | {
934 | mTargetView = target;
935 | mTargetSet = true;
936 | return this;
937 | }
938 |
939 | /**
940 | * Set the view for the prompt to focus on using the given resource id.
941 | *
942 | * @return This Builder object to allow for chaining of calls to set methods
943 | */
944 | public Builder setTarget(@IdRes final int target)
945 | {
946 | mTargetView = mActivity.findViewById(target);
947 | mTargetSet = mTargetView != null;
948 | return this;
949 | }
950 |
951 | /**
952 | * Set the centre point as a screen position
953 | * @param left Centre point from screen left
954 | * @param top Centre point from screen top
955 | * @return This Builder object to allow for chaining of calls to set methods
956 | */
957 | public Builder setTarget(final float left, final float top)
958 | {
959 | mTargetView = null;
960 | mCentreLeft = left;
961 | mCentreTop = top;
962 | mTargetSet = true;
963 | return this;
964 | }
965 |
966 | /**
967 | * Has the target been set successfully?
968 | *
969 | * @return True if set successfully.
970 | */
971 | public boolean isTargetSet()
972 | {
973 | return mTargetSet;
974 | }
975 |
976 | /**
977 | * Set the primary text using the given resource id.
978 | *
979 | * @return This Builder object to allow for chaining of calls to set methods
980 | */
981 | public Builder setPrimaryText(@StringRes final int resId)
982 | {
983 | mPrimaryText = mActivity.getString(resId);
984 | return this;
985 | }
986 |
987 | /**
988 | * Set the primary text to the given string
989 | *
990 | * @return This Builder object to allow for chaining of calls to set methods
991 | */
992 | public Builder setPrimaryText(final String text)
993 | {
994 | mPrimaryText = text;
995 | return this;
996 | }
997 |
998 | /**
999 | * Set the primary text font size using the given resource id.
1000 | *
1001 | * @return This Builder object to allow for chaining of calls to set methods
1002 | */
1003 | public Builder setPrimaryTextSize(@DimenRes final int resId)
1004 | {
1005 | mPrimaryTextSize = mActivity.getResources().getDimension(resId);
1006 | return this;
1007 | }
1008 |
1009 | /**
1010 | * Set the primary text font size.
1011 | *
1012 | * @return This Builder object to allow for chaining of calls to set methods
1013 | */
1014 | public Builder setPrimaryTextSize(final float size)
1015 | {
1016 | mPrimaryTextSize = size;
1017 | return this;
1018 | }
1019 |
1020 | /**
1021 | * Set the primary text colour.
1022 | *
1023 | * @return This Builder object to allow for chaining of calls to set methods
1024 | */
1025 | public Builder setPrimaryTextColour(@ColorInt final int colour)
1026 | {
1027 | mPrimaryTextColour = colour;
1028 | return this;
1029 | }
1030 |
1031 | /**
1032 | * Set the primary text colour using the given resource id.
1033 | *
1034 | * @return This Builder object to allow for chaining of calls to set methods
1035 | */
1036 | public Builder setPrimaryTextColourFromRes(@ColorRes final int resId)
1037 | {
1038 | mPrimaryTextColour = getColour(resId);
1039 | return this;
1040 | }
1041 |
1042 | /**
1043 | * Set the secondary text using the given resource id.
1044 | *
1045 | * @return This Builder object to allow for chaining of calls to set methods
1046 | */
1047 | public Builder setSecondaryText(@StringRes final int resId)
1048 | {
1049 | mSecondaryText = mActivity.getString(resId);
1050 | return this;
1051 | }
1052 |
1053 | /**
1054 | * Set the secondary text.
1055 | *
1056 | * @return This Builder object to allow for chaining of calls to set methods
1057 | */
1058 | public Builder setSecondaryText(final String text)
1059 | {
1060 | mSecondaryText = text;
1061 | return this;
1062 | }
1063 |
1064 | /**
1065 | * Set the secondary text font size using the give resource id.
1066 | *
1067 | * @return This Builder object to allow for chaining of calls to set methods
1068 | */
1069 | public Builder setSecondaryTextSize(@DimenRes final int resId)
1070 | {
1071 | mSecondaryTextSize = mActivity.getResources().getDimension(resId);
1072 | return this;
1073 | }
1074 |
1075 | /**
1076 | * Set the secondary text font size.
1077 | *
1078 | * @return This Builder object to allow for chaining of calls to set methods
1079 | */
1080 | public Builder setSecondaryTextSize(final float size)
1081 | {
1082 | mSecondaryTextSize = size;
1083 | return this;
1084 | }
1085 |
1086 | /**
1087 | * Set the secondary text colour.
1088 | *
1089 | * @return This Builder object to allow for chaining of calls to set methods
1090 | */
1091 | public Builder setSecondaryTextColour(@ColorInt final int colour)
1092 | {
1093 | mSecondaryTextColour = colour;
1094 | return this;
1095 | }
1096 |
1097 | /**
1098 | * Set the secondary text colour using the give resource id.
1099 | *
1100 | * @return This Builder object to allow for chaining of calls to set methods
1101 | */
1102 | public Builder setSecondaryTextColourFromRes(@ColorRes final int resId)
1103 | {
1104 | mSecondaryTextColour = getColour(resId);
1105 | return this;
1106 | }
1107 |
1108 | /**
1109 | * Set the text left and right padding.
1110 | *
1111 | * @return This Builder object to allow for chaining of calls to set methods
1112 | */
1113 | public Builder setTextPadding(final float padding)
1114 | {
1115 | mTextPadding = padding;
1116 | return this;
1117 | }
1118 |
1119 | /**
1120 | * Set the text left and right padding using the given resource id.
1121 | *
1122 | * @return This Builder object to allow for chaining of calls to set methods
1123 | */
1124 | public Builder setTextPadding(@DimenRes final int resId)
1125 | {
1126 | mTextPadding = mActivity.getResources().getDimension(resId);
1127 | return this;
1128 | }
1129 |
1130 | /**
1131 | * Set the distance between the primary and secondary text.
1132 | *
1133 | * @return This Builder object to allow for chaining of calls to set methods
1134 | */
1135 | public Builder setTextSeparation(final float separation)
1136 | {
1137 | mTextSeparation = separation;
1138 | return this;
1139 | }
1140 |
1141 | /**
1142 | * Set the distance between the primary and secondary text using the given
1143 | * resource id.
1144 | *
1145 | * @return This Builder object to allow for chaining of calls to set methods
1146 | */
1147 | public Builder setTextSeparation(@DimenRes final int resId)
1148 | {
1149 | mTextSeparation = mActivity.getResources().getDimension(resId);
1150 | return this;
1151 | }
1152 |
1153 | /**
1154 | * Set the padding between the text and the focal point.
1155 | *
1156 | * @return This Builder object to allow for chaining of calls to set methods
1157 | */
1158 | public Builder setFocalToTextPadding(final float padding)
1159 | {
1160 | mFocalToTextPadding = padding;
1161 | return this;
1162 | }
1163 |
1164 | /**
1165 | * Set the padding between the text and the focal point using the given
1166 | * resource id.
1167 | *
1168 | * @return This Builder object to allow for chaining of calls to set methods
1169 | */
1170 | public Builder setFocalToTextPadding(@DimenRes final int resId)
1171 | {
1172 | mFocalToTextPadding = mActivity.getResources().getDimension(resId);
1173 | return this;
1174 | }
1175 |
1176 | /**
1177 | * Set the interpolator to use in animations.
1178 | *
1179 | * @return This Builder object to allow for chaining of calls to set methods
1180 | */
1181 | public Builder setAnimationInterpolator(final Interpolator interpolator)
1182 | {
1183 | mAnimationInterpolator = interpolator;
1184 | return this;
1185 | }
1186 |
1187 | /**
1188 | * Set the icon to draw in the focal point using the given resource id.
1189 | *
1190 | * @return This Builder object to allow for chaining of calls to set methods
1191 | */
1192 | public Builder setIcon(@DrawableRes final int resId)
1193 | {
1194 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1195 | {
1196 | mIconDrawable = mActivity.getDrawable(resId);
1197 | }
1198 | else
1199 | {
1200 | //noinspection deprecation
1201 | mIconDrawable = mActivity.getResources().getDrawable(resId);
1202 | }
1203 | if (mIconDrawable != null)
1204 | {
1205 | mIconDrawable.setBounds(0, 0, mIconDrawable.getIntrinsicWidth(), mIconDrawable.getIntrinsicHeight());
1206 | mIconDrawable.setColorFilter(mBackgroundColour, PorterDuff.Mode.MULTIPLY);
1207 | mIconDrawable.setAlpha(Color.alpha(mBackgroundColour));
1208 | }
1209 | return this;
1210 | }
1211 |
1212 | /**
1213 | * Set the icon to draw in the focal point.
1214 | *
1215 | * @return This Builder object to allow for chaining of calls to set methods
1216 | */
1217 | public Builder setIconDrawable(final Drawable drawable)
1218 | {
1219 | mIconDrawable = drawable;
1220 | return this;
1221 | }
1222 |
1223 | /**
1224 | * Set the listener to listen for when the prompt is touched.
1225 | *
1226 | * @return This Builder object to allow for chaining of calls to set methods
1227 | */
1228 | public Builder setOnHidePromptListener(final OnHidePromptListener listener)
1229 | {
1230 | mOnHidePromptListener = listener;
1231 | return this;
1232 | }
1233 |
1234 | /**
1235 | * Set if the prompt should stop touch events on the focal point from passing
1236 | * to underlying views. Default is false.
1237 | *
1238 | * @return This Builder object to allow for chaining of calls to set methods
1239 | */
1240 | public Builder setCaptureTouchEventOnFocal(final boolean captureTouchEvent)
1241 | {
1242 | mCaptureTouchEventOnFocal = captureTouchEvent;
1243 | return this;
1244 | }
1245 |
1246 | /**
1247 | * Set the max width that the primary and secondary text can be.
1248 | *
1249 | * @return This Builder object to allow for chaining of calls to set methods
1250 | */
1251 | public Builder setMaxTextWidth(final float width)
1252 | {
1253 | mMaxTextWidth = width;
1254 | return this;
1255 | }
1256 |
1257 | /**
1258 | * Set the max width that the primary and secondary text can be using the given
1259 | * resource id.
1260 | *
1261 | * @return This Builder object to allow for chaining of calls to set methods
1262 | */
1263 | public Builder setMaxTextWidth(@DimenRes final int resId)
1264 | {
1265 | mMaxTextWidth = mActivity.getResources().getDimension(resId);
1266 | return this;
1267 | }
1268 |
1269 | /**
1270 | * Set the background colour.
1271 | *
1272 | * @return This Builder object to allow for chaining of calls to set methods
1273 | */
1274 | public Builder setBackgroundColour(@ColorInt final int colour)
1275 | {
1276 | mBackgroundColour = colour;
1277 | return this;
1278 | }
1279 |
1280 | /**
1281 | * Set the background colour using the given resource id.
1282 | *
1283 | * @return This Builder object to allow for chaining of calls to set methods
1284 | */
1285 | public Builder setBackgroundColourFromRes(@ColorRes final int resId)
1286 | {
1287 | mBackgroundColour = getColour(resId);
1288 | return this;
1289 | }
1290 |
1291 | /**
1292 | * Set the focal point colour.
1293 | *
1294 | * @return This Builder object to allow for chaining of calls to set methods
1295 | */
1296 | public Builder setFocalColour(@ColorInt final int colour)
1297 | {
1298 | mFocalColour = colour;
1299 | return this;
1300 | }
1301 |
1302 | /**
1303 | * Set the focal point colour using the given resource id.
1304 | *
1305 | * @return This Builder object to allow for chaining of calls to set methods
1306 | */
1307 | public Builder setFocalColourFromRes(@ColorRes final int resId)
1308 | {
1309 | mFocalColour = getColour(resId);
1310 | return this;
1311 | }
1312 |
1313 | /**
1314 | * Set the focal point radius.
1315 | *
1316 | * @return This Builder object to allow for chaining of calls to set methods
1317 | */
1318 | public Builder setFocalRadius(final float radius)
1319 | {
1320 | mFocalRadius = radius;
1321 | return this;
1322 | }
1323 |
1324 | /**
1325 | * Set the focal point radius using the given resource id.
1326 | *
1327 | * @return This Builder object to allow for chaining of calls to set methods
1328 | */
1329 | public Builder setFocalRadius(@DimenRes final int resId)
1330 | {
1331 | mFocalRadius = mActivity.getResources().getDimension(resId);
1332 | return this;
1333 | }
1334 |
1335 | /**
1336 | * Creates an {@link MaterialTapTargetPrompt} with the arguments supplied to this
1337 | * builder.
1338 | *
1339 | * Calling this method does not display the prompt. If no additional
1340 | * processing is needed, {@link #show()} may be called instead to both
1341 | * create and display the prompt.
1342 | *
1343 | *
1344 | * Will return {@link null} if a valid target has not been set or the primary text is {@link null}.
1345 | * To check that a valid target has been set call {@link #isTargetSet()}.
1346 | *
1432 | * Will return {@link null} if a valid target has not been set or the primary text is {@link null}.
1433 | * To check that a valid target has been set call {@link #isTargetSet()}.
1434 | *
1435 | */
1436 | public MaterialTapTargetPrompt show()
1437 | {
1438 | final MaterialTapTargetPrompt mPrompt = create();
1439 | if (mPrompt != null)
1440 | {
1441 | mPrompt.show();
1442 | }
1443 | return mPrompt;
1444 | }
1445 |
1446 | private int getColour(final int resId)
1447 | {
1448 | final int colour;
1449 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1450 | {
1451 | colour = mActivity.getColor(resId);
1452 | }
1453 | else
1454 | {
1455 | //noinspection deprecation
1456 | colour = mActivity.getResources().getColor(resId);
1457 | }
1458 | return colour;
1459 | }
1460 | }
1461 |
1462 | /**
1463 | * Interface definition for a callback to be invoked when a {@link MaterialTapTargetPrompt} is removed from view.
1464 | */
1465 | public interface OnHidePromptListener
1466 | {
1467 | /**
1468 | * Called when the use touches the prompt view,
1469 | * but before the prompt is removed from view.
1470 | *
1471 | * @param event The touch event that triggered the dismiss or finish.
1472 | * @param tappedTarget True if the prompt focal point was touched.
1473 | */
1474 | void onHidePrompt(final MotionEvent event, final boolean tappedTarget);
1475 |
1476 | /**
1477 | * Called after the prompt has been removed from view.
1478 | */
1479 | void onHidePromptComplete();
1480 | }
1481 |
1482 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
1483 | private static class AnimatorListener implements Animator.AnimatorListener
1484 | {
1485 |
1486 | @Override
1487 | public void onAnimationStart(Animator animation)
1488 | {
1489 |
1490 | }
1491 |
1492 | @Override
1493 | public void onAnimationEnd(Animator animation)
1494 | {
1495 |
1496 | }
1497 |
1498 | @Override
1499 | public void onAnimationCancel(Animator animation)
1500 | {
1501 |
1502 | }
1503 |
1504 | @Override
1505 | public void onAnimationRepeat(Animator animation)
1506 | {
1507 |
1508 | }
1509 | }
1510 | }
1511 |
--------------------------------------------------------------------------------