├── .gitignore ├── .idea ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── vcs.xml ├── README.md ├── art ├── customize_message_layout.png └── demo.gif ├── build.gradle ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── co │ │ └── kyash │ │ └── androidspotinstructions │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── co │ │ │ └── kyash │ │ │ └── androidtargetinstructions │ │ │ └── example │ │ │ ├── CustomUsageActivity.kt │ │ │ ├── MainActivity.kt │ │ │ └── SimpleUsageActivity.kt │ └── res │ │ ├── drawable │ │ ├── ic_arrow_back_white_24dp.xml │ │ ├── ic_search_white_24dp.xml │ │ ├── img_caret_bottom_black.xml │ │ ├── img_caret_top_black.xml │ │ └── message_bg_black.xml │ │ ├── layout │ │ ├── activity_custom_usage.xml │ │ ├── activity_main.xml │ │ ├── activity_simple_usage.xml │ │ └── layout_instruction_simple_message_black.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── co │ └── kyash │ └── androidspotinstructions │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── co │ │ └── kyash │ │ └── targetinstructions │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── co │ │ └── kyash │ │ └── targetinstructions │ │ ├── AbstractTargetBuilder.kt │ │ ├── InstructionsView.kt │ │ ├── TargetInstructions.kt │ │ └── targets │ │ ├── SimpleTarget.kt │ │ └── Target.kt │ └── res │ ├── drawable │ ├── img_caret_bottom.xml │ ├── img_caret_top.xml │ └── message_bg.xml │ ├── layout │ └── layout_simple_message.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ └── strings.xml ├── settings.gradle └── versions.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | 10 | # not ignore 11 | !.idea/codeStyleSettings.xml 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Target Instructions 2 | 3 | [![JitPack](https://jitpack.io/v/Kyash/android-target-instructions.svg)](https://jitpack.io/#Kyash/android-target-instructions) 4 | 5 | Make easy to implement the instructions feature to your app. 6 | 7 | ![demo.gif](art/demo.gif) 8 | 9 | # Download 10 | 11 | ## Project build.gradle 12 | 13 | ```groovy 14 | allprojects { 15 | repositories { 16 | ... 17 | maven { url "https://jitpack.io" } 18 | } 19 | } 20 | ``` 21 | 22 | ## App build.gradle 23 | 24 | ```groovy 25 | dependencies { 26 | ... 27 | compile 'com.github.Kyash:android-target-instructions:LATEST_VERSION' 28 | } 29 | ``` 30 | 31 | `LATEST_VERSION` is [![JitPack](https://jitpack.io/v/Kyash/android-target-instructions.svg)](https://jitpack.io/#Kyash/android-target-instructions) 32 | 33 | # Simple Usage 34 | 35 | Create `Target` instance to set view. 36 | 37 | ```kotlin 38 | // Create Target and set 39 | val target1 = SimpleTarget.Builder(context) 40 | .setTarget(binding.fab) 41 | .setTitle("Floating Action Button") 42 | .setHighlightRadius(100f) // Circle shape 43 | .setDescription("This is the floating action button.") 44 | .build() 45 | 46 | val target2 = SimpleTarget.Builder(context) 47 | .setTarget(binding.firstText) 48 | .setTitle("First text") 49 | .setDescription("This is the first text.") 50 | .setHighlightPadding(R.dimen.simple_highlight_padding) 51 | .build() 52 | ``` 53 | 54 | Then, set them to `TargetInstructions` and call `start()` method. 55 | 56 | ```kotlin 57 | TargetInstructions.with(activity) 58 | .setTargets(arrayListOf(target1, target2)) 59 | .start() 60 | ``` 61 | 62 | That's it! 63 | 64 | # Advanced Usage 65 | 66 | ## SimpleTarget attributes 67 | 68 | You can set some attributes to `SimpleTarget` class. 69 | 70 | ```kotlin 71 | val target = SimpleTarget.Builder(context) 72 | .setTarget(binding.fab) 73 | // or you can set target by using left, top position and width, height. 74 | .setTarget(0f, 0f, 100f, 100f) 75 | .setTitle("Floating Action Button") 76 | .setDescription("This is the floating action button.") 77 | .setHighlightRadius(100f) // If you don't set this, the highlight shape would be square. 78 | .setMessageInterpolator(FastOutSlowInInterpolator()) // For message scale animation 79 | .setMessageAnimationDuration(200L) 80 | .setPositionType(Target.Position.ABOVE) // Message position 81 | .setStartDelayMillis(200L) 82 | .setListener(object: Target.OnStateChangedListener { 83 | override fun onClosed() { 84 | // Do after target is closed. Ex) Scroll to bottom 85 | } 86 | }) 87 | .build() 88 | ``` 89 | 90 | ## Customize SimpleTarget Layout 91 | 92 | You can set the custom layout file to `SimpleTarget` like [this](https://github.com/Kyash/android-target-instructions/blob/3374e0528ed352c05b7827a158f717f6e1c48a1c/example/src/main/java/co/kyash/androidtargetinstructions/example/CustomUsageActivity.kt#L68). 93 | 94 | ```kotlin 95 | SimpleTarget.Builder(context).setTarget(binding.secondText) 96 | .setTitle("Second text") 97 | .setDescription("This is the second text. This is customized instruction.") 98 | .setMessageLayoutResId(R.layout.layout_instruction_simple_message_black) // This is custom layout 99 | ``` 100 | 101 | This custom layout code is [here](https://github.com/Kyash/android-target-instructions/blob/3374e0528ed352c05b7827a158f717f6e1c48a1c/example/src/main/res/layout/layout_instruction_simple_message_black.xml). 102 | 103 | You have to put these layout id in custom layout. 104 | 105 | ![customize_message_layout.png](art/customize_message_layout.png) 106 | 107 | ## Create custom Target 108 | If you want to customize more, you can create the original `Target` class by implementing `co.kyash.targetinstructions.targets.Target` class. 109 | 110 | [SimpleTarget.kt](https://github.com/Kyash/android-target-instructions/blob/master/library/src/main/java/co/kyash/targetinstructions/targets/SimpleTarget.kt) would help you. 111 | 112 | ## TargetInstructions attributes 113 | 114 | You can set some attributes to `TargetInstructions` class. 115 | 116 | ```kotlin 117 | TargetInstructions.with(this@SimpleUsageActivity) 118 | .setTargets(arrayListOf(target1, target2)) 119 | .setFadeDuration(200L) 120 | .setFadeInterpolator(LinearOutSlowInInterpolator()) 121 | .setOverlayColorResId(R.color.black_alpha) // Background color 122 | .start() 123 | // .finish() // Call this method when you want to finish tutorial. 124 | ``` 125 | 126 | # Dependencies 127 | This library depends on Kotlin. 128 | 129 | # Thanks 130 | This library is inspired from these awesome code. Thank you so much! 131 | - https://github.com/TakuSemba/Spotlight 132 | - https://github.com/itzikBraun/TutorialView 133 | - https://github.com/sjwall/MaterialTapTargetPrompt 134 | 135 | # Contributing 136 | We are always welcome your contribution! 137 | If you find a bug or want to add new feature, please raise issue. 138 | 139 | # License 140 | 141 | ``` 142 | Copyright 2018 Kyash 143 | 144 | Licensed under the Apache License, Version 2.0 (the "License"); 145 | you may not use this file except in compliance with the License. 146 | You may obtain a copy of the License at 147 | 148 | http://www.apache.org/licenses/LICENSE-2.0 149 | 150 | Unless required by applicable law or agreed to in writing, software 151 | distributed under the License is distributed on an "AS IS" BASIS, 152 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 153 | See the License for the specific language governing permissions and 154 | limitations under the License. 155 | ``` -------------------------------------------------------------------------------- /art/customize_message_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/art/customize_message_layout.png -------------------------------------------------------------------------------- /art/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/art/demo.gif -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "${rootDir.absolutePath}/versions.gradle" 3 | 4 | repositories { 5 | google() 6 | jcenter() 7 | mavenCentral() 8 | maven { url "https://plugins.gradle.org/m2/" } 9 | maven { url "https://jitpack.io" } 10 | } 11 | dependencies { 12 | classpath "com.android.tools.build:gradle:$versions.gradleBuildTool" 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" 14 | classpath "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle" 15 | classpath "com.github.dcendents:android-maven-gradle-plugin:$versions.mavenGradle" 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { url "https://jitpack.io" } 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: "org.jlleitschuh.gradle.ktlint" 5 | 6 | def versionMajor = 0 7 | def versionMinor = 1 8 | def versionPatch = 4 9 | 10 | android { 11 | compileSdkVersion versions.compileSdk 12 | dataBinding.enabled = true 13 | 14 | defaultConfig { 15 | applicationId "co.kyash.targetinstructions" 16 | minSdkVersion versions.minSdk 17 | targetSdkVersion versions.targetSdk 18 | versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch 19 | versionName "$versionMajor.$versionMinor.$versionPatch" 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | } 22 | signingConfigs { 23 | release { 24 | storeFile file("debug.keystore") 25 | storePassword "android" 26 | keyAlias "androiddebugkey" 27 | keyPassword "android" 28 | } 29 | } 30 | dexOptions { 31 | preDexLibraries false 32 | } 33 | buildTypes { 34 | debug { 35 | applicationIdSuffix '.debug' 36 | versionNameSuffix "-debug" 37 | } 38 | release { 39 | debuggable false 40 | zipAlignEnabled true 41 | minifyEnabled false 42 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 43 | signingConfig signingConfigs.release 44 | } 45 | } 46 | testOptions { 47 | unitTests.returnDefaultValues = true 48 | } 49 | lintOptions { 50 | lintConfig file('lint.xml') 51 | textReport true 52 | textOutput 'stdout' 53 | } 54 | } 55 | 56 | dependencies { 57 | implementation project(':library') 58 | 59 | //==================== Kotlin ==================== 60 | implementation depends.kotlin.stdlib 61 | 62 | //==================== Support Library ==================== 63 | implementation depends.support.appcompat 64 | implementation depends.support.design 65 | implementation depends.support.cardview 66 | implementation depends.support.constraintLayout 67 | 68 | //==================== Structure ==================== 69 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 70 | kapt depends.binding.compiler 71 | 72 | //==================== Debug ==================== 73 | implementation(depends.crashlytics) { 74 | transitive = true 75 | } 76 | 77 | //==================== Test ==================== 78 | testImplementation depends.junit 79 | testImplementation depends.mockitoKotlin 80 | testImplementation depends.robolectric.core 81 | androidTestImplementation depends.supporttest.runner 82 | androidTestImplementation depends.supporttest.espresso 83 | androidTestImplementation depends.espresso.core 84 | androidTestImplementation depends.espresso.intents 85 | } 86 | 87 | ktlint { 88 | version = versions.ktlint 89 | android = true 90 | reporter = "checkstyle" 91 | ignoreFailures = true 92 | } 93 | 94 | apply plugin: 'kotlin-android-extensions' -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /example/src/androidTest/java/co/kyash/androidspotinstructions/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("co.kyash.targetinstructions", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/src/main/java/co/kyash/androidtargetinstructions/example/CustomUsageActivity.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.androidtargetinstructions.example 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.databinding.DataBindingUtil 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.support.v7.app.AppCompatActivity 9 | import android.view.ViewTreeObserver 10 | import co.kyash.androidtargetinstructions.example.databinding.ActivityCustomUsageBinding 11 | import co.kyash.targetinstructions.TargetInstructions 12 | import co.kyash.targetinstructions.targets.SimpleTarget 13 | import co.kyash.targetinstructions.targets.Target 14 | 15 | 16 | class CustomUsageActivity : AppCompatActivity() { 17 | 18 | companion object { 19 | fun createIntent(context: Context) = Intent(context, CustomUsageActivity::class.java) 20 | } 21 | 22 | private lateinit var binding: ActivityCustomUsageBinding 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | binding = DataBindingUtil.setContentView(this, R.layout.activity_custom_usage) 27 | 28 | binding.root.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 29 | override fun onGlobalLayout() { 30 | binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this) 31 | showInstructions() 32 | } 33 | }) 34 | 35 | binding.toolbar.setNavigationOnClickListener { 36 | finish() 37 | } 38 | 39 | binding.fab.setOnClickListener { 40 | binding.scrollview.smoothScrollTo(0, 0) 41 | Handler().postDelayed({ 42 | showInstructions() 43 | }, 150) 44 | } 45 | } 46 | 47 | private fun showInstructions() { 48 | val target1 = SimpleTarget.Builder(this@CustomUsageActivity).setTarget(binding.fab) 49 | .setTitle("Floating Action Button") 50 | .setHighlightRadius(100f) 51 | .setDescription("This is the floating action button.") 52 | .build() 53 | 54 | val target2 = SimpleTarget.Builder(this@CustomUsageActivity).setTarget(binding.firstText) 55 | .setTitle("First text") 56 | .setDescription("This is the first text. After this is hidden, the view scrolls to bottom.") 57 | .setHighlightPadding(R.dimen.simple_highlight_padding) 58 | .setListener(object : Target.OnStateChangedListener { 59 | override fun onClosed() { 60 | binding.scrollview.smoothScrollBy(0, 10000) 61 | } 62 | }) 63 | .build() 64 | 65 | val target3 = SimpleTarget.Builder(this@CustomUsageActivity).setTarget(binding.secondText) 66 | .setTitle("Second text") 67 | .setDescription("This is the second text. This is customized instruction.") 68 | .setMessageLayoutResId(R.layout.layout_instruction_simple_message_black) 69 | .setHighlightHorizontalPadding(R.dimen.space_16dp) 70 | .setStartDelayMillis(500L) 71 | .build() 72 | 73 | TargetInstructions.with(this@CustomUsageActivity) 74 | .setTargets(arrayListOf(target1, target2, target3)) 75 | .start() 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /example/src/main/java/co/kyash/androidtargetinstructions/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.androidtargetinstructions.example 2 | 3 | import android.databinding.DataBindingUtil 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import co.kyash.androidtargetinstructions.example.databinding.ActivityMainBinding 7 | 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | DataBindingUtil.setContentView(this, R.layout.activity_main).apply { 14 | simpleUsage.setOnClickListener { 15 | startActivity(SimpleUsageActivity.createIntent(this@MainActivity)) 16 | } 17 | customUsage.setOnClickListener { 18 | startActivity(CustomUsageActivity.createIntent(this@MainActivity)) 19 | } 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /example/src/main/java/co/kyash/androidtargetinstructions/example/SimpleUsageActivity.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.androidtargetinstructions.example 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.databinding.DataBindingUtil 6 | import android.net.sip.SipSession 7 | import android.os.Bundle 8 | import android.support.v4.view.animation.FastOutSlowInInterpolator 9 | import android.support.v4.view.animation.LinearOutSlowInInterpolator 10 | import android.support.v7.app.AppCompatActivity 11 | import android.view.ViewTreeObserver 12 | import co.kyash.androidtargetinstructions.example.databinding.ActivitySimpleUsageBinding 13 | import co.kyash.targetinstructions.TargetInstructions 14 | import co.kyash.targetinstructions.targets.SimpleTarget 15 | import co.kyash.targetinstructions.targets.Target 16 | 17 | 18 | class SimpleUsageActivity : AppCompatActivity() { 19 | 20 | companion object { 21 | fun createIntent(context: Context) = Intent(context, SimpleUsageActivity::class.java) 22 | } 23 | 24 | private lateinit var binding: ActivitySimpleUsageBinding 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | binding = DataBindingUtil.setContentView(this, R.layout.activity_simple_usage) 29 | 30 | binding.root.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 31 | override fun onGlobalLayout() { 32 | binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this) 33 | showInstructions() 34 | } 35 | }) 36 | 37 | binding.toolbar.setNavigationOnClickListener { 38 | finish() 39 | } 40 | 41 | binding.fab.setOnClickListener { 42 | showInstructions() 43 | } 44 | } 45 | 46 | private fun showInstructions() { 47 | // Create Target and set 48 | val target1 = SimpleTarget.Builder(this@SimpleUsageActivity) 49 | .setTarget(binding.fab) 50 | // .setTarget(0f, 0f, 100f, 100f) 51 | .setTitle("Floating Action Button") 52 | .setHighlightRadius(100f) // Circle shape 53 | .setDescription("This is the floating action button.") 54 | .build() 55 | 56 | val target2 = SimpleTarget.Builder(this@SimpleUsageActivity) 57 | .setTarget(binding.firstText) 58 | .setTitle("First text") 59 | .setMessageInterpolator(FastOutSlowInInterpolator()) 60 | .setDescription("This is the first text.") 61 | .setHighlightPadding(R.dimen.simple_highlight_padding) 62 | .setMessageAnimationDuration() 63 | .setPositionType(Target.Position.ABOVE) 64 | .setStartDelayMillis(200L) 65 | .setListener(object: Target.OnStateChangedListener { 66 | override fun onClosed() { 67 | // Do after target is closed 68 | } 69 | }) 70 | .build() 71 | 72 | TargetInstructions.with(this@SimpleUsageActivity) 73 | .setTargets(arrayListOf(target1, target2)) 74 | .setFadeDuration(200L) 75 | .setFadeInterpolator(LinearOutSlowInInterpolator()) 76 | .setOverlayColorResId(R.color.black_alpha) // Background color 77 | .start() 78 | // .finish() 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_arrow_back_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_search_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/img_caret_bottom_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/img_caret_top_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/message_bg_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_custom_usage.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 13 | 14 | 19 | 20 | 25 | 26 | 36 | 37 | 45 | 46 | 56 | 57 | 60 | 61 | 62 | 63 | 64 | 65 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 20 | 21 | 26 | 27 | 31 | 32 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_simple_usage.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 13 | 14 | 19 | 20 | 25 | 26 | 34 | 35 | 36 | 37 | 38 | 39 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/src/main/res/layout/layout_instruction_simple_message_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 29 | 30 | 37 | 38 | 46 | 47 | 48 | 49 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1BA9E1 4 | #007AAF 5 | #6EB53B 6 | #D0011B 7 | 8 | #000000 9 | #000000 10 | #EEEEEE 11 | #9E9E9E 12 | #FFFFFF 13 | 14 | -------------------------------------------------------------------------------- /example/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 16dp 5 | 32dp 6 | -16dp 7 | 16dp 8 | 9 | 20sp 10 | 16sp 11 | 16sp 12 | 13 | -------------------------------------------------------------------------------- /example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Target Instructions 3 | 4 | -------------------------------------------------------------------------------- /example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 16 | 17 | 23 | 24 | 29 | 30 | 35 | 36 | 44 | 45 | 51 | 52 | 57 | 58 | 63 | 64 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/src/test/java/co/kyash/androidspotinstructions/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyash/android-target-instructions/171473d109ac8164b41610385a216f606d28901b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 07 16:52:42 JST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'com.github.dcendents.android-maven' 4 | 5 | def versionMajor = 0 6 | def versionMinor = 1 7 | def versionPatch = 4 8 | 9 | group = 'co.kyash' 10 | version = "$versionMajor.$versionMinor.$versionPatch" 11 | 12 | android { 13 | compileSdkVersion versions.compileSdk 14 | 15 | defaultConfig { 16 | minSdkVersion versions.minSdk 17 | targetSdkVersion versions.targetSdk 18 | versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch 19 | versionName "$versionMajor.$versionMinor.$versionPatch" 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | debuggable false 26 | zipAlignEnabled true 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | 32 | } 33 | 34 | dependencies { 35 | //==================== Kotlin ==================== 36 | implementation depends.kotlin.stdlib 37 | 38 | //==================== Support Library ==================== 39 | implementation depends.support.appcompat 40 | 41 | //==================== Test ==================== 42 | testImplementation depends.junit 43 | testImplementation depends.mockitoKotlin 44 | testImplementation depends.robolectric.core 45 | } 46 | 47 | // build a jar with source files 48 | task sourcesJar(type: Jar) { 49 | from android.sourceSets.main.java.srcDirs 50 | classifier = 'sources' 51 | } 52 | 53 | artifacts { 54 | archives sourcesJar 55 | } -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /library/src/androidTest/java/co/kyash/targetinstructions/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("co.kyash.spotinstructions.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /library/src/main/java/co/kyash/targetinstructions/AbstractTargetBuilder.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions 2 | 3 | import android.content.Context 4 | import android.support.annotation.DimenRes 5 | import android.view.View 6 | import co.kyash.targetinstructions.targets.Target 7 | import java.lang.ref.WeakReference 8 | 9 | abstract class AbstractTargetBuilder, S : Target>( 10 | context: Context 11 | ) { 12 | 13 | internal val contextWeakReference: WeakReference = WeakReference(context) 14 | internal var viewWeakReference: WeakReference? = null 15 | 16 | internal var left = 0f 17 | internal var top = 0f 18 | internal var width = 0f 19 | internal var height = 0f 20 | 21 | internal var highlightRadius = 0f 22 | 23 | internal var highlightPaddingLeft = 0f 24 | internal var highlightPaddingTop = 0f 25 | internal var highlightPaddingRight = 0f 26 | internal var highlightPaddingBottom = 0f 27 | 28 | internal var positionType: Target.Position? = null 29 | 30 | internal var startDelayMillis = 0L 31 | 32 | internal abstract fun self(): T 33 | 34 | abstract fun build(): S 35 | 36 | fun setTarget(left: Float, top: Float, width: Float, height: Float): T { 37 | this.left = left 38 | this.top = top 39 | this.width = width 40 | this.height = height 41 | return self() 42 | } 43 | 44 | fun setTarget(view: View): T { 45 | viewWeakReference = WeakReference(view) 46 | return self() 47 | } 48 | 49 | fun setHighlightRadius(radius: Float): T { 50 | this.highlightRadius = radius 51 | return self() 52 | } 53 | 54 | fun setHighlightRadius(@DimenRes radiusResId: Int): T { 55 | val activity = contextWeakReference.get() 56 | return if (activity == null) { 57 | throw IllegalStateException("context is null") 58 | } else { 59 | setHighlightRadius(activity.resources.getDimension(radiusResId)) 60 | } 61 | } 62 | 63 | fun setPositionType(positionType: Target.Position): T { 64 | this.positionType = positionType 65 | return self() 66 | } 67 | 68 | fun setStartDelayMillis(startDelayMillis: Long): T { 69 | this.startDelayMillis = startDelayMillis 70 | return self() 71 | } 72 | 73 | fun setHighlightPadding(padding: Float): T { 74 | this.highlightPaddingLeft = padding 75 | this.highlightPaddingTop = padding 76 | this.highlightPaddingRight = padding 77 | this.highlightPaddingBottom = padding 78 | return self() 79 | } 80 | 81 | fun setHighlightPadding(@DimenRes paddingResId: Int): T { 82 | val activity = contextWeakReference.get() 83 | return if (activity == null) { 84 | throw IllegalStateException("context is null") 85 | } else { 86 | setHighlightPadding(activity.resources.getDimension(paddingResId)) 87 | } 88 | } 89 | 90 | fun setHighlightVerticalPadding(padding: Float): T { 91 | this.highlightPaddingTop = padding 92 | this.highlightPaddingBottom = padding 93 | return self() 94 | } 95 | 96 | fun setHighlightVerticalPadding(@DimenRes paddingResId: Int): T { 97 | val activity = contextWeakReference.get() 98 | return if (activity == null) { 99 | throw IllegalStateException("context is null") 100 | } else { 101 | setHighlightVerticalPadding(activity.resources.getDimension(paddingResId)) 102 | } 103 | } 104 | 105 | fun setHighlightHorizontalPadding(padding: Float): T { 106 | this.highlightPaddingLeft = padding 107 | this.highlightPaddingRight = padding 108 | return self() 109 | } 110 | 111 | fun setHighlightHorizontalPadding(@DimenRes paddingResId: Int): T { 112 | val activity = contextWeakReference.get() 113 | return if (activity == null) { 114 | throw IllegalStateException("context is null") 115 | } else { 116 | setHighlightHorizontalPadding(activity.resources.getDimension(paddingResId)) 117 | } 118 | } 119 | 120 | fun setHighlightPaddingLeft(padding: Float): T { 121 | this.highlightPaddingLeft = padding 122 | return self() 123 | } 124 | 125 | fun setHighlightPaddingLeft(@DimenRes paddingResId: Int): T { 126 | val activity = contextWeakReference.get() 127 | return if (activity == null) { 128 | throw IllegalStateException("context is null") 129 | } else { 130 | setHighlightPaddingLeft(activity.resources.getDimension(paddingResId)) 131 | } 132 | } 133 | 134 | fun setHighlightPaddingTop(padding: Float): T { 135 | this.highlightPaddingTop = padding 136 | return self() 137 | } 138 | 139 | fun setHighlightPaddingTop(@DimenRes paddingResId: Int): T { 140 | val activity = contextWeakReference.get() 141 | return if (activity == null) { 142 | throw IllegalStateException("context is null") 143 | } else { 144 | setHighlightPaddingTop(activity.resources.getDimension(paddingResId)) 145 | } 146 | } 147 | 148 | fun setHighlightPaddingRight(padding: Float): T { 149 | this.highlightPaddingRight = padding 150 | return self() 151 | } 152 | 153 | fun setHighlightPaddingRight(@DimenRes paddingResId: Int): T { 154 | val activity = contextWeakReference.get() 155 | return if (activity == null) { 156 | throw IllegalStateException("context is null") 157 | } else { 158 | setHighlightPaddingRight(activity.resources.getDimension(paddingResId)) 159 | } 160 | } 161 | 162 | fun setHighlightPaddingBottom(padding: Float): T { 163 | this.highlightPaddingBottom = padding 164 | return self() 165 | } 166 | 167 | fun setHighlightPaddingBottom(@DimenRes paddingResId: Int): T { 168 | val activity = contextWeakReference.get() 169 | return if (activity == null) { 170 | throw IllegalStateException("context is null") 171 | } else { 172 | setHighlightPaddingBottom(activity.resources.getDimension(paddingResId)) 173 | } 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /library/src/main/java/co/kyash/targetinstructions/InstructionsView.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.support.v4.content.ContextCompat 7 | import android.util.AttributeSet 8 | import android.view.View 9 | import android.widget.FrameLayout 10 | import co.kyash.targetinstructions.targets.Target 11 | 12 | class InstructionsView @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null, 15 | defStyleAttr: Int = 0 16 | ) : FrameLayout(context, attrs, defStyleAttr) { 17 | 18 | private val paint = Paint() 19 | private var currentTarget: Target? = null 20 | 21 | internal var overlayColor: Int = ContextCompat.getColor(context, R.color.default_cover) 22 | internal var listener: OnStateChangedListener? = null 23 | 24 | init { 25 | id = R.id.instructions_view 26 | bringToFront() 27 | setWillNotDraw(false) 28 | setLayerType(View.LAYER_TYPE_HARDWARE, null) 29 | setOnClickListener({ 30 | if (currentTarget != null && currentTarget!!.canClick()) { 31 | listener?.onTargetClicked() 32 | } 33 | }) 34 | } 35 | 36 | override fun onDraw(canvas: Canvas?) { 37 | super.onDraw(canvas) 38 | paint.color = overlayColor 39 | canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) 40 | 41 | if (currentTarget != null && canvas != null) { 42 | currentTarget!!.drawHighlight(canvas) 43 | } 44 | } 45 | 46 | internal fun showTarget(target: Target) { 47 | removeAllViews() 48 | addView(target.messageView) 49 | 50 | currentTarget = target 51 | currentTarget?.show() 52 | } 53 | 54 | internal fun hideTarget(target: Target) { 55 | target.hideImmediately() 56 | listener?.onTargetClosed() 57 | } 58 | 59 | interface OnStateChangedListener { 60 | fun onTargetClosed() 61 | 62 | fun onTargetClicked() 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /library/src/main/java/co/kyash/targetinstructions/TargetInstructions.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions 2 | 3 | import android.animation.Animator 4 | import android.animation.ObjectAnimator 5 | import android.animation.TimeInterpolator 6 | import android.app.Activity 7 | import android.support.annotation.ColorRes 8 | import android.support.v4.content.ContextCompat 9 | import android.view.ViewGroup 10 | import android.view.animation.DecelerateInterpolator 11 | import android.widget.FrameLayout 12 | import co.kyash.targetinstructions.targets.Target 13 | import java.lang.ref.WeakReference 14 | 15 | class TargetInstructions private constructor( 16 | activity: Activity 17 | ) { 18 | 19 | companion object { 20 | private const val INSTRUCTIONS_DURATION = 300L 21 | 22 | @ColorRes 23 | private val DEFAULT_OVERLAY_COLOR = R.color.default_cover 24 | private const val DEFAULT_FADE_DURATION = 500L 25 | private val DEFAULT_FADE_INTERPOLATOR: TimeInterpolator = DecelerateInterpolator() 26 | 27 | fun with(activity: Activity) = TargetInstructions(activity) 28 | } 29 | 30 | private val instructionsViewWeakReference: WeakReference 31 | private val activityWeakReference: WeakReference = WeakReference(activity) 32 | 33 | private var targets = ArrayList() 34 | private var fadeDuration = DEFAULT_FADE_DURATION 35 | private var fadeInterpolator = DEFAULT_FADE_INTERPOLATOR 36 | private var overlayColorResId = DEFAULT_OVERLAY_COLOR 37 | 38 | init { 39 | val decorView = activity.window.decorView 40 | val instructionsView = InstructionsView(activity).apply { 41 | this@apply.overlayColor = ContextCompat.getColor(context, overlayColorResId) 42 | this@apply.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) 43 | this@apply.listener = object : InstructionsView.OnStateChangedListener { 44 | override fun onTargetClosed() { 45 | if (targets.isNotEmpty()) { 46 | targets.removeAt(0) 47 | if (targets.isNotEmpty()) { 48 | showNextTarget() 49 | } else { 50 | finishInstruction() 51 | } 52 | } 53 | } 54 | 55 | override fun onTargetClicked() { 56 | hideCurrentTarget() 57 | } 58 | } 59 | } 60 | instructionsViewWeakReference = WeakReference(instructionsView) 61 | (decorView as ViewGroup).addView(instructionsView) 62 | } 63 | 64 | fun setTargets(targets: List): TargetInstructions = apply { 65 | this.targets.clear() 66 | this.targets.addAll(targets) 67 | } 68 | 69 | fun setOverlayColorResId(@ColorRes overlayColorResId: Int) = apply { this.overlayColorResId = overlayColorResId } 70 | 71 | fun setFadeDuration(fadeDuration: Long) = apply { this.fadeDuration = fadeDuration } 72 | 73 | fun setFadeInterpolator(fadeInterpolator: TimeInterpolator) = apply { this.fadeInterpolator = fadeInterpolator } 74 | 75 | fun start() { 76 | startInstruction() 77 | } 78 | 79 | fun finish() { 80 | val activity = activityWeakReference.get() ?: return 81 | val decorView = activity.window.decorView 82 | (decorView as ViewGroup).removeView(instructionsViewWeakReference.get()) 83 | } 84 | 85 | private fun showNextTarget() { 86 | if (targets.isNotEmpty()) { 87 | instructionsViewWeakReference.get()?.showTarget(targets[0]) 88 | } 89 | } 90 | 91 | private fun hideCurrentTarget() { 92 | if (targets.isNotEmpty()) { 93 | val target = targets[0] 94 | instructionsViewWeakReference.get()?.hideTarget(target) 95 | } 96 | } 97 | 98 | private fun startInstruction() { 99 | ObjectAnimator.ofFloat(instructionsViewWeakReference.get(), "alpha", 0f, 1f).apply { 100 | duration = INSTRUCTIONS_DURATION 101 | addListener(object : Animator.AnimatorListener { 102 | override fun onAnimationRepeat(animation: Animator?) {} 103 | 104 | override fun onAnimationEnd(animation: Animator?) { 105 | showNextTarget() 106 | } 107 | 108 | override fun onAnimationCancel(animation: Animator?) {} 109 | 110 | override fun onAnimationStart(animation: Animator?) {} 111 | }) 112 | }.start() 113 | } 114 | 115 | private fun finishInstruction() { 116 | ObjectAnimator.ofFloat(instructionsViewWeakReference.get(), "alpha", 1f, 0f).apply { 117 | duration = INSTRUCTIONS_DURATION 118 | addListener(object : Animator.AnimatorListener { 119 | override fun onAnimationRepeat(animation: Animator?) {} 120 | 121 | override fun onAnimationEnd(animation: Animator?) { 122 | finish() 123 | } 124 | 125 | override fun onAnimationCancel(animation: Animator?) {} 126 | 127 | override fun onAnimationStart(animation: Animator?) {} 128 | }) 129 | 130 | }.start() 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /library/src/main/java/co/kyash/targetinstructions/targets/SimpleTarget.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions.targets 2 | 3 | import android.content.Context 4 | import android.support.annotation.LayoutRes 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewTreeObserver 8 | import android.view.animation.Animation 9 | import android.view.animation.Interpolator 10 | import android.view.animation.OvershootInterpolator 11 | import android.view.animation.ScaleAnimation 12 | import android.widget.FrameLayout 13 | import android.widget.ImageView 14 | import android.widget.LinearLayout 15 | import android.widget.TextView 16 | import co.kyash.targetinstructions.AbstractTargetBuilder 17 | import co.kyash.targetinstructions.R 18 | 19 | class SimpleTarget( 20 | override var left: Float, 21 | override var top: Float, 22 | override var width: Float, 23 | override var height: Float, 24 | override val highlightRadius: Float, 25 | override val highlightPaddingLeft: Float, 26 | override val highlightPaddingTop: Float, 27 | override val highlightPaddingRight: Float, 28 | override val highlightPaddingBottom: Float, 29 | override val messageView: View, 30 | override val targetView: View? = null, 31 | override val delay: Long = 0L, 32 | override var positionType: Position? = null, 33 | private val messageAnimationDuration: Long = 300L, 34 | private val messageInterpolator: Interpolator, 35 | private val listener: OnStateChangedListener? 36 | ) : Target( 37 | left, 38 | top, 39 | width, 40 | height, 41 | highlightRadius, 42 | highlightPaddingLeft, 43 | highlightPaddingTop, 44 | highlightPaddingRight, 45 | highlightPaddingBottom, 46 | messageView, 47 | targetView, 48 | delay, 49 | positionType 50 | ) { 51 | 52 | override fun showImmediately() { 53 | val container = messageView.findViewById(R.id.container) 54 | val topCaret = container.findViewById(R.id.top_caret) 55 | val bottomCaret = container.findViewById(R.id.bottom_caret) 56 | 57 | when (positionType) { 58 | Position.ABOVE -> { 59 | if (container.height > 0) { 60 | bottomCaret.x = getCenterX() - bottomCaret.width / 2 61 | bottomCaret.visibility = View.VISIBLE 62 | container.y = top - container.height.toFloat() - highlightPaddingTop 63 | animateMessage(container.y) 64 | } else { 65 | messageView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 66 | override fun onGlobalLayout() { 67 | messageView.viewTreeObserver.removeOnGlobalLayoutListener(this) 68 | bottomCaret.x = getCenterX() - bottomCaret.width / 2 69 | bottomCaret.visibility = View.VISIBLE 70 | container.y = top - container.height.toFloat() - highlightPaddingTop 71 | animateMessage(container.y) 72 | } 73 | }) 74 | } 75 | } 76 | Position.BELOW -> { 77 | topCaret.x = getCenterX() - topCaret.width / 2 78 | topCaret.visibility = View.VISIBLE 79 | container.y = top + height + highlightPaddingBottom 80 | animateMessage(container.y) 81 | } 82 | } 83 | } 84 | 85 | override fun hideImmediately() { 86 | this.listener?.onClosed() 87 | } 88 | 89 | private fun animateMessage(pivotY: Float) { 90 | val animation = ScaleAnimation(0.5f, 1.0f, 0.5f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.ABSOLUTE, pivotY).apply { 91 | this.interpolator = messageInterpolator 92 | repeatCount = 0 93 | duration = this@SimpleTarget.messageAnimationDuration 94 | fillAfter = true 95 | setAnimationListener(object : Animation.AnimationListener { 96 | override fun onAnimationRepeat(animation: Animation?) { 97 | // 98 | } 99 | 100 | override fun onAnimationEnd(animation: Animation?) { 101 | // 102 | } 103 | 104 | override fun onAnimationStart(animation: Animation?) { 105 | messageView.visibility = View.VISIBLE 106 | } 107 | 108 | }) 109 | } 110 | messageView.startAnimation(animation) 111 | } 112 | 113 | class Builder(context: Context) : AbstractTargetBuilder(context) { 114 | 115 | private lateinit var title: CharSequence 116 | private lateinit var description: CharSequence 117 | 118 | @LayoutRes 119 | private var messageLayoutResId = R.layout.layout_simple_message 120 | 121 | private var messageAnimationDuration: Long = 300L 122 | 123 | private var messageInterpolator: Interpolator = OvershootInterpolator() 124 | 125 | private var listener: OnStateChangedListener? = null 126 | 127 | override fun self(): Builder = this 128 | 129 | fun setTitle(title: CharSequence): Builder = apply { this.title = title } 130 | 131 | fun setDescription(description: CharSequence): Builder = apply { this.description = description } 132 | 133 | fun setListener(listener: OnStateChangedListener): Builder = apply { this.listener = listener } 134 | 135 | fun setMessageLayoutResId(@LayoutRes messageLayoutResId: Int) = apply { this.messageLayoutResId = messageLayoutResId } 136 | 137 | fun setMessageAnimationDuration(duration: Long) = apply { this.messageAnimationDuration = duration } 138 | 139 | fun setMessageInterpolator(interpolator: Interpolator) = apply { this.messageInterpolator = interpolator } 140 | 141 | override fun build(): SimpleTarget { 142 | val context = contextWeakReference.get() 143 | if (context == null) { 144 | throw IllegalStateException("activity is null") 145 | } else { 146 | val targetView = viewWeakReference?.get() 147 | 148 | val messageView = LayoutInflater.from(context).inflate(messageLayoutResId, null).apply { 149 | layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT) 150 | (findViewById(R.id.title)).text = title 151 | (findViewById(R.id.description)).text = description 152 | } 153 | 154 | return SimpleTarget( 155 | left = left, 156 | top = top, 157 | width = width, 158 | height = height, 159 | highlightRadius = highlightRadius, 160 | highlightPaddingLeft = highlightPaddingLeft, 161 | highlightPaddingTop = highlightPaddingTop, 162 | highlightPaddingRight = highlightPaddingRight, 163 | highlightPaddingBottom = highlightPaddingBottom, 164 | messageView = messageView, 165 | targetView = targetView, 166 | delay = startDelayMillis, 167 | positionType = positionType, 168 | messageAnimationDuration = messageAnimationDuration, 169 | messageInterpolator = messageInterpolator, 170 | listener = listener 171 | ) 172 | } 173 | } 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /library/src/main/java/co/kyash/targetinstructions/targets/Target.kt: -------------------------------------------------------------------------------- 1 | package co.kyash.targetinstructions.targets 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.os.Build 6 | import android.os.Handler 7 | import android.view.View 8 | import android.view.WindowManager 9 | 10 | abstract class Target( 11 | open var left: Float, 12 | open var top: Float, 13 | open var width: Float, 14 | open var height: Float, 15 | open val highlightRadius: Float, 16 | open val highlightPaddingLeft: Float, 17 | open val highlightPaddingTop: Float, 18 | open val highlightPaddingRight: Float, 19 | open val highlightPaddingBottom: Float, 20 | open val messageView: View, 21 | open val targetView: View? = null, 22 | open val delay: Long = 0L, 23 | open var positionType: Position? = null 24 | ) { 25 | 26 | private val targetPaint = Paint().apply { 27 | xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) 28 | } 29 | 30 | internal fun show() { 31 | Handler().postDelayed({ 32 | updateCoordinate() 33 | 34 | if (positionType == null) { 35 | positionType = decideMessagePositionType() 36 | } 37 | 38 | showImmediately() 39 | }, delay) 40 | } 41 | 42 | internal abstract fun showImmediately() 43 | 44 | internal abstract fun hideImmediately() 45 | 46 | internal open fun drawHighlight(canvas: Canvas) { 47 | val left = this.left - this.highlightPaddingLeft 48 | val top = this.top - this.highlightPaddingTop 49 | val right = this.left + this.width + this.highlightPaddingRight 50 | val bottom = this.top + this.height + this.highlightPaddingBottom 51 | 52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 53 | canvas.drawRoundRect(left, top, right, bottom, highlightRadius, highlightRadius, targetPaint) 54 | } else { 55 | if (highlightRadius >= Math.max(width, height) / 4) { 56 | canvas.drawCircle(this.left + width / 2, this.top + height / 2, highlightRadius, targetPaint) 57 | } else { 58 | canvas.drawRect(left, top, right, bottom, targetPaint) 59 | } 60 | } 61 | } 62 | 63 | private fun updateCoordinate() { 64 | if (targetView != null) { 65 | val location = IntArray(2) 66 | targetView!!.getLocationInWindow(location) 67 | left = location[0].toFloat() 68 | top = location[1].toFloat() 69 | width = targetView!!.width.toFloat() 70 | height = targetView!!.height.toFloat() 71 | } 72 | } 73 | 74 | internal open fun canClick(): Boolean = true 75 | 76 | private fun decideMessagePositionType(): Position { 77 | val screenSize = Point() 78 | (messageView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(screenSize) 79 | return if (top > screenSize.y - (top + height)) Position.ABOVE else Position.BELOW 80 | } 81 | 82 | internal fun getCenterX() = left + width / 2 83 | 84 | internal fun getCenterY() = top + height / 2 85 | 86 | interface OnStateChangedListener { 87 | fun onClosed() 88 | } 89 | 90 | enum class Position { 91 | ABOVE, 92 | BELOW 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /library/src/main/res/drawable/img_caret_bottom.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/img_caret_top.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/message_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/layout/layout_simple_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 26 | 27 | 33 | 34 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #80000000 4 | #FFFFFF 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8dp 5 | 16dp 6 | 7 | 16dp 8 | 9 | 8dp 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android Spot Instructions 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':example', ':library' 2 | -------------------------------------------------------------------------------- /versions.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | versions = [ 3 | compileSdk : 26, 4 | buildTools : "26.0.1", 5 | minSdk : 19, 6 | targetSdk : 26, 7 | gradleBuildTool: "3.0.1", 8 | mavenGradle : "2.0", 9 | kotlin : "1.2.10", 10 | ktlint : "0.14.0", 11 | ktlintGradle : "3.0.0", 12 | supportLibrary : "26.1.0", 13 | espresso : "3.0.1", 14 | ] 15 | 16 | depends = [ 17 | kotlin : [ 18 | stdlib: "org.jetbrains.kotlin:kotlin-stdlib-jre7:$versions.kotlin", 19 | ], 20 | 21 | //==================== Support Library ==================== 22 | support : [ 23 | appcompat : "com.android.support:appcompat-v7:$versions.supportLibrary", 24 | design : "com.android.support:design:$versions.supportLibrary", 25 | cardview : "com.android.support:cardview-v7:$versions.supportLibrary", 26 | constraintLayout: "com.android.support.constraint:constraint-layout:1.0.2" 27 | ], 28 | 29 | crashlytics : "com.crashlytics.sdk.android:crashlytics:2.8.0@aar", 30 | 31 | //==================== Structure ==================== 32 | binding : [ 33 | compiler: "com.android.databinding:compiler:3.0.1", 34 | ], 35 | 36 | //==================== Test ==================== 37 | junit : "junit:junit:4.12", 38 | mockitoKotlin: "com.nhaarman:mockito-kotlin:1.5.0", 39 | robolectric : [ 40 | core: "org.robolectric:robolectric:3.5.1", 41 | ], 42 | supporttest : [ 43 | runner : "com.android.support.test:runner:1.0.1", 44 | espresso: "com.android.support.test.espresso:espresso-core:3.0.1" 45 | ], 46 | espresso : [ 47 | core : "com.android.support.test.espresso:espresso-core:$versions.espresso", 48 | intents: "com.android.support.test.espresso:espresso-intents:$versions.espresso" 49 | ], 50 | ] 51 | } --------------------------------------------------------------------------------