├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── Ripple.gif ├── Ripple.png ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── eudycontreras │ │ └── indicatoreffect │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── eudycontreras │ │ │ └── indicatoreffect │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── circle.xml │ │ ├── ic_launcher_background.xml │ │ └── shape_circle.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── eudycontreras │ └── indicatoreffect │ └── ExampleUnitTest.kt ├── build.gradle ├── effect_rec_1.mov ├── effect_rec_2.mov ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── indicator.png ├── indicatoreffectlib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── eudycontreras │ │ └── indicatoreffectlib │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── eudycontreras │ │ │ └── indicatoreffectlib │ │ │ ├── Bounds.java │ │ │ ├── Property.java │ │ │ ├── particles │ │ │ ├── Particle.java │ │ │ └── ParticleIndicator.java │ │ │ ├── utilities │ │ │ ├── ColorUtility.java │ │ │ └── DimensionUtility.java │ │ │ └── views │ │ │ └── IndicatorView.java │ └── res │ │ └── values │ │ └── attrs.xml │ └── test │ └── java │ └── com │ └── eudycontreras │ └── indicatoreffectlib │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Eudy Contreras 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Indicator Effect Library 3 | 4 | [![platform](https://img.shields.io/badge/platform-Android-green.svg)](https://www.android.com) 5 | [![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21s) 6 | [![License: ISC](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/ISC) 7 | [![](https://jitpack.io/v/EudyContreras/Indicator-Effect-View.svg)](https://jitpack.io/#EudyContreras/Indicator-Effect-View) 8 | ![Version](https://img.shields.io/github/release/EudyContreras/Indicator-Effect-View.svg?style=flat) 9 | ![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat) 10 | 11 | 12 | #### This library allows you to display a CTA Call To Action indicator effect as an overlay to any view. The indicator effect is fully customizeable and supports a variety of options. 13 | 14 | ![Indicator Effect Image][IndicatorImage] 15 | 16 | [IndicatorImage]: https://github.com/EudyContreras/Ripple-Effect-View/blob/EudyContreras-readme/indicator.png 17 | 18 | 19 | ## About: 20 | 21 | This indicator effect view allows for a high variety of customizations in order to fit the needs of all users. It is simple to use and easy to customize. Here are some of the features of the indicator effect view. 22 | 23 | * Create an effect with an unlimited amount of indicator ripples given a specified time 24 | * Create a indicator effect with a border 25 | * Control the minimum and maximum opacity of the indicator ripple from start to end 26 | * Interpolate the indicator ripple between a start and end color 27 | * Choose between a circular or rectangular indicator ripple with possibility of rounded corners. 28 | * Choose between indicator ripples, filled indicator ripples, or outline indicator ripples. 29 | * Make the indicator ripple animation repeat as many times as you want. 30 | * Add an action to be perform upon starting a indicator effect animation 31 | * Add an action to be perform upon the end of a indicator effect animation 32 | * Add an interpolator to the ripple animation 33 | * Etc... 34 | 35 | ## APIs and customization: 36 | 37 | In code **APIs** offered by **Indicator View**. 38 | 39 | The indicator effect has a high number of apis that give full control of how the ripple is shown to the user. 40 | 41 | ### Some of the in Code Code methods: 42 | |APIs | Description| 43 | |---|---| 44 | |**startIndicatorAnimation**| *Starts the indicator animation* | 45 | |**stopIndicatorAnimation**| *Stop the indicator animation* | 46 | |**setUpWith**| *Sets a parent root to the indicator view* | 47 | |**setTarget**| *Sets the target view or target position* | 48 | |**setOnEnd**| *Executes the wrapped code upon ending the indicator animation* | 49 | |**setOnStart**| *Executes the wrapped code upon starting the indicator animation* | 50 | |**setIndicatorDuration**| *Sets the duration of the indicator animation* | 51 | |**setDrawListener**| *Sets a listener for when the view is first drawn* | 52 | |**setIndicatorType**| *Sets the type of indicator. Ex: IndicatorView.INDICATOR_TYPE_AROUND* | 53 | |**setIndicatorColor**| *Sets the color of the indicator effect* | 54 | |**setIndicatorRepeats**| *Sets how many times the indicator will repeat* | 55 | |**setIndicatorRepeatMode**| *Sets the repeat mode. Ex: IndicatorView.REPEAT_RESTART_MODE* | 56 | |**setIndicatorRippleCount**| *Sets the amount of indicator to display* | 57 | |**setIndicatorMinOpacity**| *Sets the min opacity a indicator can have* | 58 | |**setIndicatorMaxOpacity**| *Sets the max opacity a indicator can have* | 59 | |**setIndicatorStrokeWidth**| *Sets the width in pixels of the stroke used for outline style* | 60 | 61 | 62 | 63 | **XML** Layout **APIs** offered by the **Indicator View**. 64 | 65 | The indicator view can be added directly on the xml layout. The properties below will allow you to modify the indicator to your taste. 66 | 67 | ### Some of the in XML IndicatorView properties: 68 | 69 | |Property | Description| 70 | |---|---| 71 | | **iv_indicatorType:** |The type of indicator to be shown, The indicator can be outlined or filled.| 72 | | **iv_indicatorRepeatMode:** |The repeat mode of how the animation should be repeated.| 73 | | **iv_indicatorShapeType:** |The indicator can be circular or rectangular.| 74 | | **iv_autoStartAnimation:** |Determines whether the indicator animation should start automatically.| 75 | | **iv_indicatorDuration:** |Determines the amount of time that the indicator animation should last.| 76 | | **iv_indicatorColor:** |Determines the color the indicator should have.| 77 | | **iv_indicatorStrokeColor:** |Determines the border color of the indicator if using indicator border.| 78 | | **iv_indicatorColorStart:** |Determines the start color the indicator should have if interpolation enabled.| 79 | | **iv_indicatorColorEnd:** |Determines the ending color the indicator should have if interpolation enabled.| 80 | | **iv_indicatorCount:** |Determines the amount of indicator to animate.| 81 | | **iv_indicatorStrokeWidth:** |Determine the width of the strokes that are shown if outline is chosen| 82 | | **iv_indicatorClipRadius:** |Determines how big the clipped area radius will be for the Indicator ripple| 83 | | **iv_indicatorClipWidth:** |Determines how big the clipped area width will be for the Indicator ripple 84 | | **iv_indicatorClipHeight:** |Determines how big the clipped area height will be for the Indicator ripple| 85 | | **iv_indicatoraxOpacity:** |Determines the max amount of opacity a indicator can have| 86 | | **iv_indicatorMinOpacity:** |Determines the lowest amount of opacity a indicator can have| 87 | | **iv_indicatorMaxRadius:** |Determines the hightest radius a indicator can have| 88 | | **iv_indicatorMinRadius:** |Determines the lowest radius a indicator can have| 89 | | **iv_indicatorMinWidth:** |Determines the lowest width a rectangular indicator can have| 90 | | **iv_indicatorMaxWidth:** |Determines the highest width a rectangular indicator can have| 91 | | **iv_indicatorMinHeight:** |Determines the lowest height a rectangular indicator can have| 92 | | **iv_indicatorMaxHeight:** |Determines the highest height a rectangular indicator can have| 93 | | **iv_indicatorRepeatCount:** |Determines the amount of times a indicator will | 94 | | **iv_indicatorCornerRadius:** |Determines how rounded the corners of a rectangular indicator is | 95 | 96 | 97 | ## How to use it? 98 | 99 | #### Step 1 100 | 101 | Add it in your root build.gradle at the end of repositories: 102 | 103 | ``` groovy 104 | 105 | dependencies { 106 | classpath 'com.github.dcendents:android-maven-gradle-plugin:${version}' 107 | } 108 | 109 | allprojects { 110 | repositories { 111 | maven { url 'https://jitpack.io' } 112 | } 113 | } 114 | ``` 115 | 116 | Add as a dependency in you applications build.gradle. 117 | 118 | ``` groovy 119 | 120 | dependencies { 121 | implementation "com.github.EudyContreras:Indicator-Effect-View:v1.1.0" 122 | } 123 | ``` 124 | 125 | #### Step 2 126 | 127 | Add the Ripple View to your layout: 128 | 129 | ``` xml 130 | 140 | ``` 141 | 142 | Or add the indicator directly through code: 143 | 144 | ``` java 145 | IndicatorView indicator = new IndicatorView(this); 146 | 147 | indicator.setIndicatorType(IndicatorView.INDICATOR_TYPE_AROUND); 148 | indicator.setIndicatorClipRadius(DimensionUtility.convertDpToPixel(this,50)); 149 | indicator.setIndicatorColor(ContextCompat.getColor(this,R.color.accent)); 150 | indicator.setIndicatorCount(3); 151 | indicator.setIndicatorMinOpacity(0.1f); 152 | indicator.setIndicatorMaxOpacity(0.8f); 153 | indicator.setIndicatorRepeatMode(IndicatorView.REPEAT_MODE_RESTART); 154 | indicator.setIndicatorRepeats(IndicatorView.INFINITE_REPEATS); 155 | indicator.setIndicatorDuration(2500); 156 | indicator.setUpWith(findViewById(R.id.login_activity)); 157 | ``` 158 | 159 | 160 | When using the IndicatorView through code it is importatnt to set a target at the right place. The target must be set once the view's dimensions and location have been computed. 161 | 162 | 163 | 164 | ``` java 165 | 166 | private float radiusRatio = 1.7; 167 | private float clipRatio = 0.5; 168 | 169 | void onPause(){ 170 | super.onPause(); 171 | indicator.stopIndicatorAnimation() 172 | } 173 | 174 | void onResume() { 175 | super.onResume() 176 | 177 | View view = findViewById(R.id.someElement); 178 | 179 | view.post { 180 | indicator.setTarget(this, view, radiusRatio, clipRatio) 181 | indicator.startIndicatorAnimation() 182 | } 183 | } 184 | ``` 185 | 186 | ## Authors: 187 | 188 | **Eudy Contreras** 189 | 190 | ## Contact: 191 | 192 | If you wish to contact you may reach me through my [Linked](https://www.linkedin.com/in/eudycontreras/) or my [Email](EudyContrerasRosario@gmail.com) 193 | 194 | ## Future works: 195 | 196 | There are parts of this library that are yet to be finished and there are also some things which I plan to add to the library. These things will be shown here along with popular demands. 197 | 198 | - [ ] Port the code kotlin 199 | - [ ] Add to different repositories besides jitpack 200 | - [ ] Allow the user to set a repeat interval/wait time 201 | - [ ] Add builder pattern for creating indicator 202 | - [ ] Allow the effect to translate with its target upon animations 203 | 204 | ## License: 205 | 206 | This project is licensed under the MIT License - see the [Licence](https://github.com/EudyContreras/RippleEffect/blob/EudyContreras-readme/LICENSE) file for details 207 | -------------------------------------------------------------------------------- /Ripple.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/Ripple.gif -------------------------------------------------------------------------------- /Ripple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/Ripple.png -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.eudycontreras.indicatorffect" 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility 1.8 25 | targetCompatibility 1.8 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 32 | implementation 'androidx.appcompat:appcompat:1.0.0-beta01' 33 | implementation 'androidx.core:core-ktx:1.1.0-alpha04' 34 | implementation 'androidx.constraintlayout:constraintlayout:1.1.2' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' 38 | implementation project(':indicatoreffectlib') 39 | } 40 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/eudycontreras/indicatoreffect/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffect 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getTargetContext() 20 | assertEquals("com.eudycontreras.rippleeffect", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/eudycontreras/indicatoreffect/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffect 2 | 3 | import android.animation.Animator 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.util.Log 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.view.animation.DecelerateInterpolator 11 | import android.view.animation.OvershootInterpolator 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.core.content.ContextCompat 14 | import com.eudycontreras.indicatoreffectlib.utilities.DimensionUtility 15 | import com.eudycontreras.indicatoreffectlib.views.IndicatorView 16 | import kotlinx.android.synthetic.main.activity_main.* 17 | 18 | class MainActivity : AppCompatActivity() { 19 | 20 | private lateinit var indicator: IndicatorView 21 | private val interpolatorIn = DecelerateInterpolator() 22 | private val interpolatorOut = OvershootInterpolator() 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_main) 27 | 28 | 29 | indicator = IndicatorView(this, findViewById(R.id.root)) 30 | indicator.indicatorType = IndicatorView.INDICATOR_TYPE_AROUND 31 | indicator.indicatorColor = ContextCompat.getColor(this, R.color.colorAccent) 32 | indicator.indicatorStrokeColor = ContextCompat.getColor(this, R.color.colorAccent) 33 | indicator.indicatorColorStart = ContextCompat.getColor(this, R.color.white) 34 | indicator.indicatorColorEnd = ContextCompat.getColor(this, R.color.colorAccent) 35 | indicator.indicatorCount = 3 36 | indicator.indicatorMinOpacity = 0f 37 | indicator.indicatorMaxOpacity = 1f 38 | indicator.indicatorRepeatMode = IndicatorView.REPEAT_MODE_RESTART 39 | indicator.indicatorRepeats = IndicatorView.INFINITE_REPEATS 40 | indicator.indicatorDuration = 7000 41 | indicator.indicatorStrokeWidth = 0f 42 | indicator.isShowBorderStroke = false 43 | indicator.revealDuration = 0 44 | indicator.isUseColorInterpolation = false 45 | indicator.offsetY = DimensionUtility.convertPixelsToDp(this, 10f) 46 | 47 | } 48 | 49 | override fun onResume() { 50 | super.onResume() 51 | val view: View = findViewById(R.id.someElement) 52 | 53 | someElementContainer.post { 54 | 55 | someElementContainer.pivotX = someElementContainer.measuredWidth / 2f 56 | someElementContainer.pivotY = someElementContainer.measuredHeight / 2f 57 | someElementContainer.scaleX = 0.75f 58 | someElementContainer.scaleY = 0.75f 59 | someElementContainer.translationZ = 0f 60 | Handler().postDelayed({ 61 | someElementContainer.animate() 62 | .setInterpolator(interpolatorOut) 63 | .translationZ(DimensionUtility.convertDpToPixel(this, DimensionUtility.convertPixelsToDp(this,8f))) 64 | .scaleY(1f) 65 | .scaleX(1f) 66 | .setDuration(500L) 67 | .setListener(object: Animator.AnimatorListener { 68 | override fun onAnimationRepeat(p0: Animator?) { } 69 | 70 | override fun onAnimationEnd(p0: Animator?) { 71 | if (!indicator.isAnimationRunning) { 72 | indicator.setTarget(someElementContainer, 2.05f, 0.45f) 73 | indicator.startIndicatorAnimation(0) 74 | 75 | } 76 | } 77 | 78 | override fun onAnimationCancel(p0: Animator?) {} 79 | 80 | override fun onAnimationStart(p0: Animator?) {} 81 | }) 82 | .start() 83 | },1000) 84 | 85 | } 86 | 87 | view.setOnClickListener { 88 | Log.d("CLICKED", "CLICKED") 89 | indicator.startIndicatorAnimation() 90 | } 91 | 92 | view.setOnTouchListener { _, motionEvent -> 93 | when(motionEvent.action) { 94 | MotionEvent.ACTION_DOWN -> { 95 | someElementContainer.animate() 96 | .setInterpolator(interpolatorIn) 97 | .setListener(null) 98 | .translationZ(DimensionUtility.convertDpToPixel(this,0f)) 99 | .scaleY(0.93f) 100 | .scaleX(0.93f) 101 | .setDuration(150L) 102 | .start() 103 | } 104 | MotionEvent.ACTION_UP -> { 105 | someElementContainer.animate() 106 | .setInterpolator(interpolatorOut) 107 | .setListener(null) 108 | .translationZ(DimensionUtility.convertDpToPixel(this,8f)) 109 | .scaleY(1f) 110 | .scaleX(1f) 111 | .setDuration(150L) 112 | .start() 113 | view.callOnClick() 114 | } 115 | MotionEvent.ACTION_MOVE -> { 116 | someElementContainer.animate() 117 | .setInterpolator(interpolatorOut) 118 | .setListener(null) 119 | .translationZ(DimensionUtility.convertDpToPixel(this,8f)) 120 | .scaleY(1f) 121 | .scaleX(1f) 122 | .setDuration(150L) 123 | .start() 124 | } 125 | MotionEvent.ACTION_CANCEL -> { 126 | someElementContainer.animate() 127 | .setInterpolator(interpolatorOut) 128 | .setListener(null) 129 | .translationZ(DimensionUtility.convertDpToPixel(this,8f)) 130 | .scaleY(1f) 131 | .scaleX(1f) 132 | .setDuration(150L) 133 | .start() 134 | } 135 | else -> {} 136 | } 137 | false 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 33 | 39 | 40 | 41 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #004281 4 | #00376c 5 | #0096d7 6 | #FFA50101 7 | #FF8F00 8 | #FFFFFF 9 | #201F1F 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Indicator Effect 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/eudycontreras/indicatoreffect/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffect 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.20' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.3.0' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /effect_rec_1.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/effect_rec_1.mov -------------------------------------------------------------------------------- /effect_rec_2.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/effect_rec_2.mov -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 03 14:32:10 CET 2019 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.10.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EudyContreras/Indicator-Effect-View/20e4db655e529ce5736f5ef5dae5f640e55feb7f/indicator.png -------------------------------------------------------------------------------- /indicatoreffectlib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /indicatoreffectlib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 21 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = '1.8' 26 | targetCompatibility = '1.8' 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | 34 | implementation 'com.android.support:appcompat-v7:28.0.0' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 38 | 39 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 40 | implementation 'com.android.support:support-annotations:28.0.0' 41 | } 42 | 43 | repositories { 44 | mavenCentral() 45 | } 46 | apply plugin: 'com.github.dcendents.android-maven' 47 | 48 | group='com.github.EudyContreras' -------------------------------------------------------------------------------- /indicatoreffectlib/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 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/androidTest/java/com/eudycontreras/indicatoreffectlib/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | @Test 19 | public void useAppContext() { 20 | // Context of the app under test. 21 | Context appContext = InstrumentationRegistry.getTargetContext(); 22 | 23 | assertEquals("com.eudycontreras.rippleeffectlib.test", appContext.getPackageName()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/Bounds.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib; 2 | 3 | import androidx.annotation.RestrictTo; 4 | 5 | /** 6 | * Note: Unlicensed private property of the author and creator 7 | * unauthorized use of this class outside of the Indicator Effect project 8 | * by the author may result on legal prosecution. 9 | *

10 | * Created by Eudy Contreras 11 | * 12 | * @author Eudy Contreras 13 | * @version 1.0 14 | * @since 2018-03-31 15 | */ 16 | @RestrictTo(RestrictTo.Scope.LIBRARY) 17 | public class Bounds { 18 | 19 | private final float x; 20 | private final float y; 21 | 22 | private final float width; 23 | private final float height; 24 | 25 | public Bounds(float x, float y, float width, float height) { 26 | this.x = x; 27 | this.y = y; 28 | this.width = width; 29 | this.height = height; 30 | } 31 | 32 | public float getX() { 33 | return x; 34 | } 35 | 36 | public float getY() { 37 | return y; 38 | } 39 | 40 | public float getWidth() { 41 | return width; 42 | } 43 | 44 | public float getHeight() { 45 | return height; 46 | } 47 | 48 | public boolean inRange(float sourceX, float sourceY){ 49 | return (sourceX >= x && sourceX < width) && (sourceY >= y && sourceY < height); 50 | } 51 | 52 | public boolean inRange(float sourceX, float sourceY, float radius){ 53 | return (sourceX >= (x-radius) && sourceX < (width + radius)) && (sourceY >= (y-radius) && sourceY < (height + radius)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/Property.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib; 2 | 3 | import androidx.annotation.RestrictTo; 4 | 5 | /** 6 | * Note: Unlicensed private property of the author and creator 7 | * unauthorized use of this class outside of the Indicator Effect project 8 | * by the author may result on legal prosecution. 9 | *

10 | * Created by Eudy Contreras 11 | * 12 | * @author Eudy Contreras 13 | * @version 1.0 14 | * @since 2018-03-31 15 | */ 16 | @RestrictTo(RestrictTo.Scope.LIBRARY) 17 | public class Property { 18 | private T value; 19 | 20 | public Property(T value) { 21 | this.value = value; 22 | } 23 | 24 | public T getValue() { 25 | return value; 26 | } 27 | 28 | public void setValue(T value) { 29 | this.value = value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/particles/Particle.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib.particles; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import androidx.annotation.RestrictTo; 6 | import com.eudycontreras.indicatoreffectlib.Bounds; 7 | import com.eudycontreras.indicatoreffectlib.utilities.ColorUtility; 8 | 9 | /** 10 | * Note: Unlicensed private property of the author and creator 11 | * unauthorized use of this class outside of the Indicator Effect project 12 | * by the author may result on legal prosecution. 13 | *

14 | * Created by Eudy Contreras 15 | * 16 | * @author Eudy Contreras 17 | * @version 1.0 18 | * @since 2018-03-31 19 | */ 20 | @RestrictTo(RestrictTo.Scope.LIBRARY) 21 | public abstract class Particle { 22 | 23 | public final static float DEFAULT_LIFE_TIME = 5; 24 | public final static float DEFAULT_VELOCITY = 5; 25 | 26 | protected float centerX; 27 | protected float centerY; 28 | 29 | protected float targetX = Integer.MIN_VALUE; 30 | protected float targetY = Integer.MIN_VALUE; 31 | 32 | protected float velX; 33 | protected float velY; 34 | 35 | protected float varianceX; 36 | protected float varianceY; 37 | 38 | protected float radiusRatio = 0.0f; 39 | protected float actualRadius; 40 | protected float radius; 41 | protected float spacing; 42 | protected float opacity = 1.0f; 43 | 44 | protected float lifeSpan = 1.0f; 45 | protected float decay; 46 | 47 | protected boolean visible; 48 | protected boolean killed; 49 | protected boolean fade = true; 50 | protected boolean shrink = true; 51 | protected boolean checkBounds = false; 52 | protected boolean alwaysAlive = false; 53 | 54 | protected Bounds bounds; 55 | protected Paint paint; 56 | 57 | protected ColorUtility.SoulColor color; 58 | protected ColorUtility.SoulColor colorStart; 59 | protected ColorUtility.SoulColor colorEnd; 60 | protected ColorUtility.SoulColor strokeColor; 61 | protected ColorUtility.SoulColor innerOutlineColor; 62 | 63 | protected Particle(float lifeTime, float x, float y, float velX, float velY, float varianceX, float varianceY, float radius, int color, Paint paint, Bounds bounds) { 64 | this.centerX = x; 65 | this.centerY = y; 66 | this.velX = velX; 67 | this.velY = velY; 68 | this.varianceX = varianceX; 69 | this.varianceY = varianceY; 70 | this.radius = radius; 71 | this.color = ColorUtility.toSoulColor(color); 72 | this.bounds = bounds; 73 | this.decay = 0.016f / lifeTime; 74 | this.paint = paint; 75 | } 76 | 77 | protected Particle(float lifeTime, float x, float y, float velX, float velY, float radius, int color, Paint paint, Bounds bounds) { 78 | this(lifeTime, x, y, velX, velY, 0, 0, radius, color, paint, bounds); 79 | } 80 | 81 | protected Particle(float lifeTime, float x, float y, float radius, int color, Paint paint, Bounds bounds) { 82 | this(lifeTime, x, y,0, 0, radius, color,paint, bounds); 83 | } 84 | 85 | protected Particle(float x, float y, float radius, int color, Paint paint, Bounds bounds) { 86 | this(Integer.MAX_VALUE, x, y,0, 0, radius, color,paint, bounds); 87 | } 88 | 89 | protected Particle(float x, float y, float radius, int color, Bounds bounds) { 90 | this(Integer.MAX_VALUE, x, y,0, 0, radius, color,new Paint(), bounds); 91 | } 92 | 93 | public abstract void init(); 94 | 95 | protected abstract void draw(Canvas canvas); 96 | 97 | public void update(){ 98 | centerX += (velX + varianceX); 99 | centerY += (velY + varianceY); 100 | 101 | if(killed) { 102 | lifeSpan -= decay; 103 | } 104 | } 105 | 106 | public void update(float duration, float time){ 107 | 108 | velX = targetX != Integer.MIN_VALUE ? ((targetX - centerX) / duration) : velX; 109 | velY = targetY != Integer.MIN_VALUE ? ((targetY - centerY) / duration) : velY; 110 | 111 | centerX += ((velX + varianceX) * time); 112 | centerY += ((velY + varianceY) * time); 113 | 114 | if(shrink){ 115 | radiusRatio = time; 116 | radius = actualRadius * radiusRatio; 117 | } 118 | 119 | if(killed) { 120 | lifeSpan -= (decay * time); 121 | }else{ 122 | if(fade) { 123 | opacity = time; 124 | } 125 | } 126 | } 127 | 128 | public float checkDistanceTo(Particle particle) { 129 | 130 | float distanceX = this.centerX+radius - particle.getCenterX()+radius; 131 | float distanceY = this.centerY+radius - particle.getCenterY()+radius; 132 | 133 | return distanceX*distanceX + distanceY*distanceY; 134 | } 135 | 136 | public boolean isAlive() { 137 | if(alwaysAlive){ 138 | return true; 139 | }else{ 140 | if(bounds != null) { 141 | return (!checkBounds || bounds.inRange(centerX, centerY, (radius * 2))) && (lifeSpan > 0) && (radius > 0) && (opacity > 0); 142 | } 143 | return lifeSpan > 0; 144 | } 145 | } 146 | 147 | public Paint getPaint() { 148 | return paint; 149 | } 150 | 151 | public void setVisible(boolean visible) { 152 | this.visible = visible; 153 | } 154 | 155 | public boolean isVisible() { 156 | return visible; 157 | } 158 | 159 | public boolean isFade() { 160 | return fade; 161 | } 162 | 163 | public void setFade(boolean fade) { 164 | this.fade = fade; 165 | } 166 | 167 | public float getOpacity() { 168 | return opacity; 169 | } 170 | 171 | public void setOpacity(float opacity) { 172 | this.opacity = opacity; 173 | } 174 | 175 | public boolean isShrink() { 176 | return shrink; 177 | } 178 | 179 | public void setShrink(boolean shrink) { 180 | this.shrink = shrink; 181 | } 182 | 183 | public float getTargetX() { 184 | return targetX; 185 | } 186 | 187 | public void setTargetX(float targetX) { 188 | this.targetX = targetX; 189 | } 190 | 191 | public float getTargetY() { 192 | return targetY; 193 | } 194 | 195 | public void setTargetY(float targetY) { 196 | this.targetY = targetY; 197 | } 198 | 199 | public float getCenterX() { 200 | return centerX; 201 | } 202 | 203 | public void setCenterX(float centerX) { 204 | this.centerX = centerX; 205 | } 206 | 207 | public float getCenterY() { 208 | return centerY; 209 | } 210 | 211 | public void setCenterY(float centerY) { 212 | this.centerY = centerY; 213 | } 214 | 215 | public float getVelX() { 216 | return velX; 217 | } 218 | 219 | public void setVelX(float velX) { 220 | this.velX = velX; 221 | } 222 | 223 | public float getVelY() { 224 | return velY; 225 | } 226 | 227 | public void setVelY(float velY) { 228 | this.velY = velY; 229 | } 230 | 231 | public float getVarianceX() { 232 | return varianceX; 233 | } 234 | 235 | public void setVarianceX(float varianceX) { 236 | this.varianceX = varianceX; 237 | } 238 | 239 | public float getVarianceY() { 240 | return varianceY; 241 | } 242 | 243 | public void setVarianceY(float varianceY) { 244 | this.varianceY = varianceY; 245 | } 246 | 247 | public float getRadius() { 248 | return radius; 249 | } 250 | 251 | public void setRadius(float radius) { 252 | this.radius = radius; 253 | this.actualRadius = radius; 254 | } 255 | 256 | public ColorUtility.SoulColor getColor() { 257 | return color; 258 | } 259 | 260 | public void setColor(ColorUtility.SoulColor color) { 261 | this.color = color; 262 | } 263 | 264 | public void setStrokeColor(ColorUtility.SoulColor color) { 265 | this.strokeColor = color; 266 | } 267 | 268 | public void setInnerOutlineColor(ColorUtility.SoulColor innerOutlineColor) { 269 | this.innerOutlineColor = innerOutlineColor; 270 | } 271 | 272 | public ColorUtility.SoulColor getStrokeColor() { 273 | return strokeColor; 274 | } 275 | 276 | public ColorUtility.SoulColor getColorStart() { 277 | return colorStart; 278 | } 279 | 280 | public void setColorStart(ColorUtility.SoulColor colorStart) { 281 | this.colorStart = colorStart; 282 | } 283 | 284 | public ColorUtility.SoulColor getColorEnd() { 285 | return colorEnd; 286 | } 287 | 288 | public void setColorEnd(ColorUtility.SoulColor colorEnd) { 289 | this.colorEnd = colorEnd; 290 | } 291 | 292 | public float getDecay() { 293 | return decay; 294 | } 295 | 296 | public void setDecay(float decay) { 297 | this.decay = 0.016f / decay; 298 | } 299 | 300 | public Bounds getBounds() { 301 | return bounds; 302 | } 303 | 304 | public void setBounds(Bounds bounds) { 305 | this.bounds = bounds; 306 | } 307 | 308 | public void setPaint(Paint paint) { 309 | this.paint = paint; 310 | } 311 | 312 | public boolean isKilled() { 313 | return killed; 314 | } 315 | 316 | public void setKilled(boolean killed) { 317 | this.killed = killed; 318 | } 319 | 320 | public boolean isCheckBounds() { 321 | return checkBounds; 322 | } 323 | 324 | public void setCheckBounds(boolean checkBounds) { 325 | this.checkBounds = checkBounds; 326 | } 327 | 328 | public boolean isAlwaysAlive() { 329 | return alwaysAlive; 330 | } 331 | 332 | public void setAlwaysAlive(boolean alwaysAlive) { 333 | this.alwaysAlive = alwaysAlive; 334 | } 335 | 336 | public void setSpacing(float spacing) { 337 | this.spacing = spacing; 338 | } 339 | 340 | public float getSpacing(){ 341 | return spacing; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/particles/ParticleIndicator.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib.particles; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.Region; 8 | import android.os.Build; 9 | import androidx.annotation.RestrictTo; 10 | import com.eudycontreras.indicatoreffectlib.Bounds; 11 | import com.eudycontreras.indicatoreffectlib.utilities.ColorUtility; 12 | import com.eudycontreras.indicatoreffectlib.views.IndicatorView; 13 | 14 | /** 15 | * Note: Unlicensed private property of the author and creator 16 | * unauthorized use of this class outside of the Indicator Effect project 17 | * by the author may result on legal prosecution. 18 | *

19 | * Created by Eudy Contreras 20 | * 21 | * @author Eudy Contreras 22 | * @version 1.0 23 | * @since 2018-03-31 24 | */ 25 | @RestrictTo(RestrictTo.Scope.LIBRARY) 26 | public class ParticleIndicator extends Particle { 27 | 28 | public static final int RIPPLE_TYPE_OUTLINE = 0; 29 | public static final int RIPPLE_TYPE_FILLED = 1; 30 | public static final int RIPPLE_TYPE_INDICATOR = 2; 31 | 32 | private float cornerRadius = 0; 33 | 34 | private float x; 35 | private float y; 36 | 37 | private float width; 38 | private float height; 39 | 40 | private float maxWidth; 41 | private float maxHeight; 42 | 43 | private float minWidth; 44 | private float minHeight; 45 | 46 | private float maxRadius; 47 | private float minRadius; 48 | 49 | private float minOpacity; 50 | private float maxOpacity; 51 | 52 | private float clipRadius; 53 | 54 | private float clipWidhtRatio; 55 | private float clipHeightRatio; 56 | 57 | private float strokeWidth; 58 | 59 | private float innerOutlineWidth; 60 | 61 | private float interpolation; 62 | 63 | private int shapeType; 64 | private int type; 65 | 66 | private Path clipPath; 67 | 68 | public ParticleIndicator() { 69 | super(0, 0, 0, 0, null, null); 70 | } 71 | 72 | public ParticleIndicator(float x, float y, float radius, int color, Paint paint, Bounds bounds) { 73 | super(x, y, radius, color, paint, bounds); 74 | } 75 | 76 | public ParticleIndicator(float x, float y, float radius, int color, Bounds bounds) { 77 | super(x, y, radius, color, bounds); 78 | } 79 | 80 | public void init() { 81 | clipPath = new Path(); 82 | clipPath.reset(); 83 | if (shapeType == IndicatorView.INDICATOR_SHAPE_CIRCLE) { 84 | clipPath.addCircle(centerX, centerY, clipRadius, Path.Direction.CCW); 85 | } else { 86 | float top = y - (minHeight / 2); 87 | float left = x - (minWidth / 2); 88 | float bottom = y + (minHeight / 2); 89 | float right = x + (minWidth / 2); 90 | clipPath.addRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, Path.Direction.CCW); 91 | } 92 | } 93 | 94 | @Override 95 | public void update() { 96 | 97 | } 98 | 99 | @Override 100 | public void update(float duration, float time) { 101 | if (shapeType == IndicatorView.INDICATOR_SHAPE_CIRCLE) { 102 | radius = minRadius + ((maxRadius - minRadius) * time); 103 | } else { 104 | width = minWidth + ((maxWidth - minWidth) * time); 105 | height = minHeight + ((maxHeight - minHeight) * time); 106 | 107 | x = x - (width / 2); 108 | y = y - (height / 2); 109 | } 110 | 111 | opacity = minOpacity + ((maxOpacity - minOpacity) * (maxOpacity - minOpacity - time)); 112 | 113 | if (opacity < 0f) 114 | opacity = 0f; 115 | if (opacity > 1f) 116 | opacity = 1f; 117 | 118 | if (colorStart != null && colorEnd != null) { 119 | ColorUtility.interpolateColor(colorStart, colorEnd, time, color); 120 | } 121 | } 122 | 123 | @Override 124 | public boolean isAlive() { 125 | return (opacity > 0f && (radius > 0 || width > 0 || height > 0)) || alwaysAlive; 126 | } 127 | 128 | public void draw(Canvas canvas) { 129 | switch (type) { 130 | case RIPPLE_TYPE_FILLED: 131 | drawFilledRipple(canvas); 132 | break; 133 | case RIPPLE_TYPE_INDICATOR: 134 | drawIndicatorRipple(canvas); 135 | break; 136 | case RIPPLE_TYPE_OUTLINE: 137 | drawOutLineRipple(canvas); 138 | break; 139 | } 140 | 141 | if (innerOutlineColor != null) { 142 | paint.setStyle(Paint.Style.STROKE); 143 | paint.setStrokeWidth(innerOutlineWidth); 144 | paint.setColor(innerOutlineColor.toColor()); 145 | 146 | canvas.drawCircle(centerX, centerY, minRadius, paint); 147 | } 148 | } 149 | 150 | private void drawFilledRipple(Canvas canvas) { 151 | color.setAlpha(opacity); 152 | 153 | paint.setStyle(Paint.Style.FILL); 154 | paint.setColor(color.toColor()); 155 | 156 | if (shapeType == IndicatorView.INDICATOR_SHAPE_CIRCLE) { 157 | canvas.drawCircle(centerX, centerY, radius, paint); 158 | 159 | if (strokeColor != null) { 160 | strokeColor.setAlpha(opacity); 161 | 162 | paint.setStyle(Paint.Style.STROKE); 163 | paint.setStrokeWidth(strokeWidth); 164 | paint.setColor(strokeColor.toColor()); 165 | 166 | canvas.drawCircle(centerX, centerY, radius, paint); 167 | } 168 | 169 | } else { 170 | float top = y; 171 | float left = x; 172 | float bottom = y + height; 173 | float right = x + width; 174 | canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, paint); 175 | } 176 | } 177 | 178 | private void drawOutLineRipple(Canvas canvas) { 179 | color.setAlpha(opacity); 180 | 181 | paint.setStyle(Paint.Style.STROKE); 182 | paint.setStrokeWidth(strokeWidth); 183 | paint.setColor(color.toColor()); 184 | 185 | if (shapeType == IndicatorView.INDICATOR_SHAPE_CIRCLE) { 186 | canvas.drawCircle(centerX, centerY, radius, paint); 187 | } else { 188 | float top = y; 189 | float left = x; 190 | float bottom = y + height; 191 | float right = x + width; 192 | canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, paint); 193 | } 194 | } 195 | 196 | @SuppressWarnings("Deprecated") 197 | @SuppressLint("Deprecated") 198 | private void drawIndicatorRipple(Canvas canvas) { 199 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 200 | canvas.clipOutPath(clipPath); 201 | } else { 202 | canvas.clipPath(clipPath, Region.Op.DIFFERENCE); 203 | } 204 | 205 | color.setAlpha(opacity); 206 | 207 | paint.setStyle(Paint.Style.FILL); 208 | paint.setColor(color.toColor()); 209 | 210 | if (shapeType == IndicatorView.INDICATOR_SHAPE_CIRCLE) { 211 | canvas.drawCircle(centerX, centerY, radius, paint); 212 | 213 | if (strokeColor != null) { 214 | strokeColor.setAlpha(opacity); 215 | 216 | paint.setStyle(Paint.Style.STROKE); 217 | paint.setStrokeWidth(strokeWidth); 218 | paint.setColor(strokeColor.toColor()); 219 | 220 | canvas.drawCircle(centerX, centerY, radius, paint); 221 | } 222 | 223 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 224 | canvas.clipOutPath(clipPath); 225 | } else { 226 | canvas.clipPath(clipPath, Region.Op.DIFFERENCE); 227 | } 228 | } else { 229 | float top = y; 230 | float left = x; 231 | float bottom = y + height; 232 | float right = x + width; 233 | canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, paint); 234 | } 235 | } 236 | 237 | public void setMinRadius(float minRadius) { 238 | this.minRadius = minRadius; 239 | } 240 | 241 | public void setMaxRadius(float maxRadius) { 242 | this.maxRadius = maxRadius; 243 | } 244 | 245 | public void setMinOpacity(float minOpacity) { 246 | this.minOpacity = minOpacity; 247 | } 248 | 249 | public void setMaxOpacity(float minOpacity) { 250 | this.maxOpacity = minOpacity; 251 | } 252 | 253 | public void setClipRadius(float clipRadius) { 254 | this.clipRadius = clipRadius; 255 | } 256 | 257 | public void setType(int type) { 258 | this.type = type; 259 | } 260 | 261 | public void setStrokeWidth(float strokeWidth) { 262 | this.strokeWidth = strokeWidth; 263 | } 264 | 265 | public void setInnerOutlineWidth(float innerOutlineWidth) { 266 | this.innerOutlineWidth = innerOutlineWidth; 267 | } 268 | 269 | public void setCornerRadius(float cornerRadius) { 270 | this.cornerRadius = cornerRadius; 271 | } 272 | 273 | public void setShapeType(int shapeType) { 274 | this.shapeType = shapeType; 275 | } 276 | 277 | public void setMaxWidth(float width) { 278 | this.maxWidth = width; 279 | } 280 | 281 | public void setMaxHeight(float height) { 282 | this.maxHeight = height; 283 | } 284 | 285 | public void setMinWidth(float width) { 286 | this.minWidth = width; 287 | } 288 | 289 | public void setMinHeight(float height) { 290 | this.minHeight = height; 291 | } 292 | 293 | public void setY(float y) { 294 | this.y = y; 295 | } 296 | 297 | public void setX(float x) { 298 | this.x = x; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/utilities/ColorUtility.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib.utilities; 2 | 3 | import android.graphics.Color; 4 | import androidx.annotation.ColorInt; 5 | import androidx.annotation.RestrictTo; 6 | 7 | /** 8 | * Note: Unlicensed private property of the author and creator 9 | * unauthorized use of this class outside of the Indicator Effect project 10 | * by the author may result on legal prosecution. 11 | *

12 | * Created by Eudy Contreras 13 | * 14 | * @author Eudy Contreras 15 | * @version 1.0 16 | * @since 2018-03-31 17 | */ 18 | @RestrictTo(RestrictTo.Scope.LIBRARY) 19 | public class ColorUtility { 20 | 21 | public static int colorDecToHex(int r, int g, int b) { 22 | return Color.parseColor(colorDecToHexString(r, g, b)); 23 | } 24 | 25 | public static int colorDecToHex(int a, int r, int g, int b) { 26 | return Color.parseColor(colorDecToHexString(a, r, g, b)); 27 | } 28 | 29 | public static String colorDecToHexString(int r, int g, int b) { 30 | return colorDecToHexString(255,r,g,b); 31 | } 32 | 33 | public static String colorDecToHexString(int a, int r, int g, int b) { 34 | String red = Integer.toHexString(r); 35 | String green = Integer.toHexString(g); 36 | String blue = Integer.toHexString(b); 37 | String alpha = Integer.toHexString(a); 38 | 39 | if (red.length() == 1) { 40 | red = "0" + red; 41 | } 42 | if (green.length() == 1) { 43 | green = "0" + green; 44 | } 45 | if (blue.length() == 1) { 46 | blue = "0" + blue; 47 | } 48 | if (alpha.length() == 1){ 49 | alpha = "0" + alpha; 50 | } 51 | 52 | return "#" + alpha + red + green + blue; 53 | } 54 | 55 | public static int adjustAlpha(@ColorInt int color, float factor) { 56 | int alpha = Math.round(Color.alpha(color) * factor); 57 | int red = Color.red(color); 58 | int green = Color.green(color); 59 | int blue = Color.blue(color); 60 | return Color.argb(alpha, red, green, blue); 61 | } 62 | 63 | public static void adjustAlpha(SoulColor color, float factor) { 64 | color.setAlpha(Math.round(color.getAlpha() * factor)); 65 | } 66 | 67 | public static void interpolateColor(SoulColor start, SoulColor end, float amount, SoulColor result) { 68 | result.setColor(start); 69 | 70 | result.setRed((int)(start.red + ((end.red - start.red) * amount))); 71 | result.setGreen((int)(start.green + ((end.green - start.green) * amount))); 72 | result.setBlue((int)(start.blue + ((end.blue - start.blue) * amount))); 73 | } 74 | 75 | public static SoulColor toSoulColor(int color){ 76 | int alpha = Color.alpha(color); 77 | int red = Color.red(color); 78 | int green = Color.green(color); 79 | int blue = Color.blue(color); 80 | return new SoulColor(alpha, red, green, blue); 81 | } 82 | 83 | public static class SoulColor{ 84 | 85 | private int tempColor; 86 | 87 | private int red; 88 | private int green; 89 | private int blue; 90 | private int alpha; 91 | 92 | public SoulColor(int alpha, int red, int green, int blue) { 93 | this.alpha = alpha; 94 | this.red = red; 95 | this.green = green; 96 | this.blue = blue; 97 | } 98 | 99 | public SoulColor(int red, int green, int blue) { 100 | this(1,red,green,blue); 101 | } 102 | 103 | public SoulColor(int color){ 104 | this.alpha = Color.alpha(color); 105 | this.red = Color.red(color); 106 | this.green = Color.green(color); 107 | this.blue = Color.blue(color); 108 | } 109 | 110 | public SoulColor(){ 111 | this(0x000000); 112 | } 113 | 114 | public void setColor(SoulColor color){ 115 | this.alpha = color.alpha; 116 | this.red = color.red; 117 | this.green = color.green; 118 | this.blue = color.blue; 119 | } 120 | 121 | public void setColor(int color){ 122 | this.alpha = Color.alpha(color); 123 | this.red = Color.red(color); 124 | this.green = Color.green(color); 125 | this.blue = Color.blue(color); 126 | } 127 | 128 | public int getRed() { 129 | return red; 130 | } 131 | 132 | public void setRed(int red) { 133 | this.red = red; 134 | } 135 | 136 | public int getGreen() { 137 | return green; 138 | } 139 | 140 | public void setGreen(int green) { 141 | this.green = green; 142 | } 143 | 144 | public int getBlue() { 145 | return blue; 146 | } 147 | 148 | public void setBlue(int blue) { 149 | this.blue = blue; 150 | } 151 | 152 | public int getAlpha() { 153 | return alpha; 154 | } 155 | 156 | public void setAlpha(int alpha) { 157 | this.alpha = alpha; 158 | } 159 | 160 | public void setAlpha(float alpha) { 161 | this.alpha = Math.round(255f * alpha); 162 | } 163 | 164 | public int toColor(){ 165 | if(tempColor == -1){ 166 | tempColor = Color.argb(alpha,red,green,blue); 167 | return tempColor; 168 | }else{ 169 | if(colorChanged()){ 170 | tempColor = Color.argb(alpha,red,green,blue); 171 | return tempColor; 172 | } 173 | } 174 | 175 | return tempColor; 176 | } 177 | 178 | private boolean colorChanged(){ 179 | return true; 180 | } 181 | 182 | public static SoulColor copy(SoulColor color){ 183 | return new SoulColor(color.alpha,color.red,color.green,color.blue); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/utilities/DimensionUtility.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib.utilities; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | import androidx.annotation.RestrictTo; 7 | 8 | /** 9 | * Note: Unlicensed private property of the author and creator 10 | * unauthorized use of this class outside of the Indicator Effect project 11 | * by the author may result on legal prosecution. 12 | *

13 | * Created by Eudy Contreras 14 | * 15 | * @author Eudy Contreras 16 | * @version 1.0 17 | * @since 2018-03-31 18 | */ 19 | @RestrictTo(RestrictTo.Scope.LIBRARY) 20 | public class DimensionUtility { 21 | 22 | /** 23 | * This method converts dp unit to equivalent pixels, depending on device density. 24 | * 25 | * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels 26 | * @param context Context to get resources and device specific display metrics 27 | * @return A float value to represent px equivalent to dp depending on device density 28 | */ 29 | public static float convertDpToPixel(Context context, float dp){ 30 | Resources resources = context.getResources(); 31 | DisplayMetrics metrics = resources.getDisplayMetrics(); 32 | float px = dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); 33 | return px; 34 | } 35 | 36 | /** 37 | * This method converts device specific pixels to density independent pixels. 38 | * 39 | * @param px A value in px (pixels) unit. Which we need to convert into db 40 | * @param context Context to get resources and device specific display metrics 41 | * @return A float value to represent dp equivalent to px value 42 | */ 43 | public static float convertPixelsToDp(Context context, float px){ 44 | Resources resources = context.getResources(); 45 | DisplayMetrics metrics = resources.getDisplayMetrics(); 46 | float dp = px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); 47 | return dp; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/java/com/eudycontreras/indicatoreffectlib/views/IndicatorView.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib.views; 2 | 3 | import android.animation.*; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.os.Build; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.ViewParent; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | import android.view.animation.LinearInterpolator; 18 | import androidx.annotation.NonNull; 19 | import com.eudycontreras.indicatoreffectlib.Bounds; 20 | import com.eudycontreras.indicatoreffectlib.Property; 21 | import com.eudycontreras.indicatoreffectlib.R; 22 | import com.eudycontreras.indicatoreffectlib.particles.ParticleIndicator; 23 | import com.eudycontreras.indicatoreffectlib.utilities.ColorUtility; 24 | import com.eudycontreras.indicatoreffectlib.utilities.DimensionUtility; 25 | 26 | import java.util.ArrayList; 27 | 28 | /** 29 | * Note: Unlicensed private property of the author and creator 30 | * unauthorized use of this class outside of the Indicator Effect project 31 | * by the author may result on legal prosecution. 32 | *

33 | * Created by Eudy Contreras 34 | * 35 | * @author Eudy Contreras 36 | * @version 1.0 37 | * @since 2018-03-31 38 | */ 39 | public class IndicatorView extends View { 40 | 41 | @FunctionalInterface 42 | public interface IndicatorLayoutBehaviour { 43 | void setUpBehaviour(View view, int wrappedWidth, int wrappedHeight); 44 | } 45 | 46 | public static final int INDICATOR_SHAPE_CIRCLE = 0; 47 | public static final int INDICATOR_SHAPE_RECTANGLE = 1; 48 | 49 | public static final int INDICATOR_TYPE_OUTLINE = 0; 50 | public static final int INDICATOR_TYPE_FILLED = 1; 51 | public static final int INDICATOR_TYPE_AROUND = 2; 52 | 53 | public static final int INFINITE_REPEATS = ObjectAnimator.INFINITE; 54 | 55 | public static final int REPEAT_MODE_RESTART = ObjectAnimator.RESTART; 56 | public static final int REPEAT_MODE_REVERSE = ObjectAnimator.REVERSE; 57 | 58 | private int backgroundColor = Color.TRANSPARENT; 59 | 60 | private int indicatorShape = INDICATOR_SHAPE_CIRCLE; 61 | private int indicatorType; 62 | private int indicatorCount; 63 | private int indicatorColor; 64 | private int indicatorStrokeColor; 65 | private int indicatorColorStart; 66 | private int indicatorColorEnd; 67 | private int indicatorRepeats; 68 | private int indicatorRepeatMode; 69 | private int indicatorInnerOutlineColor; 70 | 71 | private int usableWidth; 72 | private int usableHeight; 73 | 74 | private float indicatorX = Integer.MIN_VALUE; 75 | private float indicatorY = Integer.MIN_VALUE; 76 | 77 | private float centerX = Integer.MIN_VALUE; 78 | private float centerY = Integer.MIN_VALUE; 79 | 80 | private float offsetX = 0f; 81 | private float offsetY = 0f; 82 | 83 | private float innerOutLineWidth; 84 | 85 | private float indicatorMinWidth; 86 | private float indicatorMinHeight; 87 | 88 | private float indicatorMaxWidth; 89 | private float indicatorMaxHeight; 90 | 91 | private float indicatorMinOpacity; 92 | private float indicatorMaxOpacity; 93 | 94 | private float indicatorClipRadius; 95 | private float indicatorMaxRadius; 96 | private float indicatorMinRadius; 97 | 98 | private float indicatorCornerRadius; 99 | 100 | private float indicatorStrokeWidth; 101 | 102 | private long revealDuration = 0; 103 | private long concealDuration = 0; 104 | private long indicatorDelay; 105 | private long indicatorDuration; 106 | private long indicatorIntervalDelay; 107 | 108 | private boolean showInnerOutline = false; 109 | private boolean useColorInterpolation = false; 110 | private boolean showBorderStroke = false; 111 | private boolean animationRunning = false; 112 | private boolean autoStartIndicator = false; 113 | private boolean cleanUpAfter = false; 114 | 115 | private ParticleIndicator[] indicators; 116 | private ArrayList animators; 117 | 118 | private Runnable onEnd; 119 | private Runnable onStart; 120 | private Interpolator indicatorInterpolator; 121 | private IndicatorLayoutBehaviour behaviour; 122 | private ViewDrawListener listener; 123 | 124 | private AnimatorSet animatorSet; 125 | private ViewGroup parent; 126 | private Bounds bounds; 127 | private Paint paint; 128 | private View target; 129 | 130 | private ColorUtility.SoulColor color; 131 | private ColorUtility.SoulColor strokeColor; 132 | private ColorUtility.SoulColor colorStart; 133 | private ColorUtility.SoulColor colorEnd; 134 | private ColorUtility.SoulColor colorInnerOutline; 135 | 136 | public IndicatorView(Context context) { 137 | super(context); 138 | initialize(null); 139 | } 140 | 141 | public IndicatorView(Context context, @NonNull ViewGroup parent) { 142 | super(context); 143 | initialize(parent); 144 | } 145 | 146 | public IndicatorView(Context context, AttributeSet attrs) { 147 | super(context, attrs); 148 | initialize(null); 149 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView); 150 | try { 151 | setUpAttributes(typedArray); 152 | } finally { 153 | typedArray.recycle(); 154 | } 155 | } 156 | 157 | public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { 158 | super(context, attrs, defStyleAttr); 159 | initialize(null); 160 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView); 161 | try { 162 | setUpAttributes(typedArray); 163 | } finally { 164 | typedArray.recycle(); 165 | } 166 | } 167 | 168 | @SuppressWarnings("unused") 169 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 170 | public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 171 | super(context, attrs, defStyleAttr, defStyleRes); 172 | initialize(null); 173 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView); 174 | try { 175 | setUpAttributes(typedArray); 176 | } finally { 177 | typedArray.recycle(); 178 | } 179 | } 180 | 181 | private void initialize(ViewGroup parentView) { 182 | 183 | paint = new Paint(); 184 | paint.setAntiAlias(true); 185 | 186 | color = new ColorUtility.SoulColor(); 187 | 188 | indicatorInterpolator = new LinearInterpolator(); 189 | 190 | animatorSet = new AnimatorSet(); 191 | animators = new ArrayList<>(); 192 | 193 | parent = parentView; 194 | 195 | initializeIndicator(); 196 | } 197 | 198 | public void setUpAttributes(TypedArray typedArray) { 199 | if (typedArray != null) { 200 | indicatorType = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorType, INDICATOR_TYPE_FILLED); 201 | indicatorCount = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorCount, 3); 202 | indicatorDuration = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorDuration, 2000); 203 | indicatorIntervalDelay = typedArray.getInt(R.styleable.IndicatorView_iv_intervalDelay, 0); 204 | indicatorColor = typedArray.getColor(R.styleable.IndicatorView_iv_indicatorColor, 0xFFFFFFFF); 205 | indicatorStrokeColor = typedArray.getColor(R.styleable.IndicatorView_iv_indicatorStrokeColor, 0xFFFFFFFF); 206 | indicatorColorStart = typedArray.getColor(R.styleable.IndicatorView_iv_indicatorColorStart, 0xFFFFFFFF); 207 | indicatorColorEnd = typedArray.getColor(R.styleable.IndicatorView_iv_indicatorColorEnd, 0xFFFFFFFF); 208 | indicatorMinOpacity = typedArray.getFloat(R.styleable.IndicatorView_iv_indicatorMinOpacity, 0f); 209 | indicatorMaxOpacity = typedArray.getFloat(R.styleable.IndicatorView_iv_indicatorMaxOpacity, 1f); 210 | indicatorClipRadius = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorClipRadius, 0f); 211 | indicatorStrokeWidth = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorStrokeWidth, DimensionUtility.convertDpToPixel(getContext(), 2.5f)); 212 | indicatorMinRadius = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMinRadius, 0); 213 | indicatorMaxRadius = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMaxRadius, DimensionUtility.convertDpToPixel(getContext(), 30)); 214 | indicatorRepeats = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorRepeatCount, ObjectAnimator.INFINITE); 215 | indicatorRepeatMode = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorRepeatMode, 0); 216 | autoStartIndicator = typedArray.getBoolean(R.styleable.IndicatorView_iv_autoStartAnimation, false); 217 | useColorInterpolation = typedArray.getBoolean(R.styleable.IndicatorView_iv_useColorInterpolation, false); 218 | showBorderStroke = typedArray.getBoolean(R.styleable.IndicatorView_iv_showBorderStroke, false); 219 | indicatorShape = typedArray.getInt(R.styleable.IndicatorView_iv_indicatorShapeType, INDICATOR_SHAPE_CIRCLE); 220 | indicatorCornerRadius = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorCornerRadius, 0f); 221 | indicatorMinWidth = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMinWidth, 0f); 222 | indicatorMinHeight = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMinHeight, 0f); 223 | indicatorMaxWidth = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMaxWidth, DimensionUtility.convertDpToPixel(getContext(), 60)); 224 | indicatorMaxHeight = typedArray.getDimension(R.styleable.IndicatorView_iv_indicatorMaxHeight, DimensionUtility.convertDpToPixel(getContext(), 30)); 225 | initializeIndicator(); 226 | } 227 | } 228 | 229 | private void initializeIndicator() { 230 | setAlpha(0); 231 | 232 | indicatorDelay = (int) ((double) indicatorDuration / (double) indicatorCount); 233 | indicators = new ParticleIndicator[indicatorCount]; 234 | 235 | for (Animator animator : animators) { 236 | animator.cancel(); 237 | } 238 | 239 | animators.clear(); 240 | color.setColor(indicatorColor); 241 | 242 | if (useColorInterpolation) { 243 | if (colorStart == null || colorEnd == null) { 244 | colorStart = new ColorUtility.SoulColor(); 245 | colorEnd = new ColorUtility.SoulColor(); 246 | } 247 | colorStart.setColor(indicatorColorStart); 248 | colorEnd.setColor(indicatorColorEnd); 249 | } else { 250 | colorStart = null; 251 | colorEnd = null; 252 | } 253 | 254 | if (showBorderStroke) { 255 | if (strokeColor == null) { 256 | strokeColor = new ColorUtility.SoulColor(); 257 | } 258 | strokeColor.setColor(indicatorStrokeColor); 259 | } else { 260 | strokeColor = null; 261 | } 262 | 263 | if (showInnerOutline) { 264 | if (colorInnerOutline == null) { 265 | colorInnerOutline = new ColorUtility.SoulColor(); 266 | } 267 | 268 | colorInnerOutline.setColor(indicatorInnerOutlineColor); 269 | } 270 | 271 | for (int i = 0; i < indicators.length; i++) { 272 | ParticleIndicator indicator = new ParticleIndicator(); 273 | indicator.setPaint(new Paint()); 274 | indicator.setShapeType(indicatorShape); 275 | indicator.setCornerRadius(indicatorCornerRadius); 276 | indicator.setMinRadius(indicatorMinRadius); 277 | indicator.setMaxRadius(indicatorMaxRadius); 278 | indicator.setMinOpacity(indicatorMinOpacity); 279 | indicator.setMaxOpacity(indicatorMaxOpacity); 280 | indicator.setClipRadius(indicatorClipRadius); 281 | indicator.setStrokeWidth(indicatorStrokeWidth); 282 | indicator.setMinWidth(indicatorMinWidth); 283 | indicator.setMinHeight(indicatorMinHeight); 284 | indicator.setMaxWidth(indicatorMaxWidth); 285 | indicator.setMaxHeight(indicatorMaxHeight); 286 | indicator.setColor(color); 287 | indicator.setStrokeColor(strokeColor); 288 | indicator.setInnerOutlineColor(colorInnerOutline); 289 | indicator.setInnerOutlineWidth(innerOutLineWidth); 290 | indicator.setColorStart(colorStart); 291 | indicator.setColorEnd(colorEnd); 292 | indicator.setType(indicatorType); 293 | indicator.setX(indicatorX + offsetX); 294 | indicator.setY(indicatorY + offsetY); 295 | indicator.setCenterX(centerX + offsetX); 296 | indicator.setCenterY(centerY + offsetY); 297 | indicator.setVisible(true); 298 | indicator.setAlwaysAlive(true); 299 | indicator.init(); 300 | 301 | indicators[i] = indicator; 302 | 303 | final int index = i; 304 | final long delay = (long) (index * indicatorDelay); 305 | final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); 306 | 307 | animator.setStartDelay(delay); 308 | animator.setRepeatCount(indicatorRepeats); 309 | animator.setRepeatMode(indicatorRepeatMode); 310 | animator.setDuration(indicatorDuration); 311 | animator.addUpdateListener(animation -> { 312 | if (indicators[index] == null) 313 | return; 314 | 315 | indicators[index].setShapeType(indicatorShape); 316 | indicators[index].setColor(color); 317 | indicators[index].setColorStart(colorStart); 318 | indicators[index].setColorEnd(colorEnd); 319 | indicators[index].setStrokeColor(strokeColor); 320 | indicators[index].setInnerOutlineColor(colorInnerOutline); 321 | indicators[index].setCornerRadius(indicatorCornerRadius); 322 | indicators[index].setMinOpacity(indicatorMinOpacity); 323 | indicators[index].setMaxOpacity(indicatorMaxOpacity); 324 | indicators[index].setMinRadius(indicatorMinRadius); 325 | indicators[index].setMaxRadius(indicatorMaxRadius); 326 | indicators[index].setClipRadius(indicatorClipRadius); 327 | indicators[index].setStrokeWidth(indicatorStrokeWidth); 328 | indicators[index].setInnerOutlineWidth(innerOutLineWidth); 329 | indicators[index].setCenterX(centerX + offsetX); 330 | indicators[index].setCenterY(centerY + offsetY); 331 | indicators[index].setX(indicatorX + offsetX); 332 | indicators[index].setY(indicatorY + offsetY); 333 | indicators[index].setMinWidth(indicatorMinWidth); 334 | indicators[index].setMinHeight(indicatorMinHeight); 335 | indicators[index].setMaxWidth(indicatorMaxWidth); 336 | indicators[index].setMaxHeight(indicatorMaxHeight); 337 | indicators[index].update(indicatorDuration, (float) animation.getAnimatedValue()); 338 | 339 | if (!indicators[index].isAlive()) { 340 | indicators[index] = null; 341 | } 342 | invalidate(); 343 | }); 344 | 345 | animators.add(animator); 346 | } 347 | } 348 | 349 | public void startIndicatorAnimation() { 350 | startIndicatorAnimation(0); 351 | } 352 | 353 | public void startIndicatorAnimation(int indicatorDelay) { 354 | startIndicatorAnimation(indicatorDelay, indicatorInterpolator); 355 | } 356 | 357 | public void startIndicatorAnimation(int indicatorDelay, Interpolator interpolator) { 358 | animatorSet.cancel(); 359 | animatorSet = null; 360 | 361 | initializeIndicator(); 362 | 363 | animatorSet = new AnimatorSet(); 364 | animatorSet.setInterpolator(interpolator); 365 | animatorSet.playTogether(animators); 366 | animatorSet.setStartDelay(indicatorDelay); 367 | animatorSet.start(); 368 | animationRunning = true; 369 | 370 | show(revealDuration); 371 | } 372 | 373 | public void stopIndicatorAnimation() { 374 | stopIndicatorAnimation(concealDuration); 375 | } 376 | 377 | public void stopIndicatorAnimation(long duration) { 378 | if (animationRunning) { 379 | dismiss(duration); 380 | } 381 | } 382 | 383 | private void show(long duration) { 384 | if (duration == 0) { 385 | if (onStart != null) { 386 | onStart.run(); 387 | } 388 | setAlpha(1); 389 | return; 390 | } 391 | 392 | ValueAnimator showAnimator = ValueAnimator.ofFloat(0f, 1f); 393 | showAnimator.addUpdateListener(animation -> setAlpha((float) animation.getAnimatedValue())); 394 | showAnimator.setDuration(duration); 395 | showAnimator.setInterpolator(new DecelerateInterpolator()); 396 | showAnimator.start(); 397 | } 398 | 399 | private void dismiss(long duration) { 400 | if (duration == 0) { 401 | animatorSet.cancel(); 402 | animationRunning = false; 403 | if (onEnd != null) { 404 | onEnd.run(); 405 | } 406 | return; 407 | } 408 | 409 | ValueAnimator showAnimator = ValueAnimator.ofFloat(1f, 0f); 410 | showAnimator.addUpdateListener(animation -> setAlpha((float) animation.getAnimatedValue())); 411 | showAnimator.setDuration(duration); 412 | showAnimator.setInterpolator(new LinearInterpolator()); 413 | showAnimator.addListener(new AnimatorListenerAdapter() { 414 | @Override 415 | public void onAnimationEnd(Animator animation) { 416 | super.onAnimationEnd(animation); 417 | animatorSet.cancel(); 418 | animationRunning = false; 419 | if (onEnd != null) { 420 | onEnd.run(); 421 | } 422 | } 423 | }); 424 | 425 | showAnimator.start(); 426 | } 427 | 428 | public void removeIndicator() { 429 | removeIndicator(0); 430 | } 431 | 432 | public void removeIndicator(long duration) { 433 | stopIndicatorAnimation(duration); 434 | parent.removeView(this); 435 | } 436 | 437 | private void initializeValues() { 438 | int width = getWidth(); 439 | int height = getHeight(); 440 | 441 | int paddingLeft = getPaddingLeft(); 442 | int paddingRight = getPaddingRight(); 443 | int paddingTop = getPaddingTop(); 444 | int paddingBottom = getPaddingBottom(); 445 | 446 | usableWidth = width - (paddingLeft + paddingRight); 447 | usableHeight = height - (paddingTop + paddingBottom); 448 | 449 | bounds = new Bounds(0, 0, usableWidth, usableHeight); 450 | } 451 | 452 | @Override 453 | protected void onDraw(Canvas canvas) { 454 | super.onDraw(canvas); 455 | canvas.drawColor(backgroundColor); 456 | 457 | if (bounds == null) { 458 | 459 | initializeValues(); 460 | 461 | for (ParticleIndicator indicator : indicators) { 462 | if (indicator == null) 463 | return; 464 | 465 | indicator.setBounds(bounds); 466 | indicator.setColor(color); 467 | indicator.setStrokeColor(strokeColor); 468 | indicator.setColorStart(colorStart); 469 | indicator.setColorEnd(colorEnd); 470 | indicator.setInnerOutlineColor(colorInnerOutline); 471 | indicator.setCornerRadius(indicatorCornerRadius); 472 | indicator.setMinOpacity(indicatorMinOpacity); 473 | indicator.setMaxOpacity(indicatorMaxOpacity); 474 | indicator.setMinRadius(indicatorMinRadius); 475 | indicator.setMaxRadius(indicatorMaxRadius); 476 | indicator.setClipRadius(indicatorClipRadius); 477 | indicator.setStrokeWidth(indicatorStrokeWidth); 478 | indicator.setInnerOutlineWidth(innerOutLineWidth); 479 | indicator.setX(indicatorX + offsetX); 480 | indicator.setY(indicatorY + offsetY); 481 | indicator.setCenterX(centerX + offsetX); 482 | indicator.setCenterY(centerY + offsetY); 483 | indicator.setMaxWidth(indicatorMaxWidth); 484 | indicator.setMaxHeight(indicatorMaxHeight); 485 | indicator.setMinWidth(indicatorMinWidth); 486 | indicator.setMinHeight(indicatorMinHeight); 487 | indicator.update(); 488 | indicator.init(); 489 | } 490 | 491 | if (listener != null) { 492 | listener.onViewDrawn(this); 493 | } 494 | 495 | if (autoStartIndicator) { 496 | startIndicatorAnimation(); 497 | } 498 | 499 | invalidate(); 500 | } 501 | 502 | for (ParticleIndicator indicator : indicators) { 503 | if (indicator != null) { 504 | indicator.draw(canvas); 505 | } 506 | } 507 | } 508 | 509 | public void setTarget(View view) { 510 | setTarget(view, 2); 511 | } 512 | 513 | public void setTarget(View view, float radiusRatio) { 514 | setTarget(view, 1.25f, 1.5f, radiusRatio, 0.5f); 515 | } 516 | 517 | public void setTarget(View view, float radiusRatio, float clipRatio) { 518 | setTarget(view, 1.25f, 1.5f, radiusRatio, clipRatio); 519 | } 520 | 521 | public void setTarget(View view, float widthRatio, float heightRatio, float radiusRatio, float clipRatio) { 522 | if (target == view) 523 | return; 524 | 525 | target = view; 526 | 527 | int width = ((ViewGroup) view.getParent()).getWidth(); 528 | int height = ((ViewGroup) view.getParent()).getHeight(); 529 | 530 | setLayoutParams(new ViewGroup.LayoutParams(width, height)); 531 | 532 | if (behaviour != null) { 533 | behaviour.setUpBehaviour(this, width, height); 534 | } 535 | 536 | if(this.getParent() != parent) { 537 | parent.removeView(this); 538 | parent.addView(this); 539 | } 540 | 541 | int[] locationView = new int[2]; 542 | 543 | view.getLocationOnScreen(locationView); 544 | 545 | locationView[0] = locationView[0] - getCalculatedOffsetX(parent); 546 | locationView[1] = locationView[1] - getCalculatedOffsetY(parent); 547 | 548 | centerX = (locationView[0] + view.getMeasuredWidth() / 2f); 549 | centerY = (locationView[1] + view.getMeasuredHeight() / 2f); 550 | 551 | indicatorX = (locationView[0] + view.getMeasuredWidth() / 2f); 552 | indicatorY = (locationView[1] + view.getMeasuredHeight() / 2f); 553 | 554 | indicatorMinWidth = view.getWidth(); 555 | indicatorMinHeight = view.getHeight(); 556 | 557 | indicatorMaxWidth = indicatorMinWidth * widthRatio; 558 | indicatorMaxHeight = indicatorMinHeight * heightRatio; 559 | 560 | indicatorClipRadius = Math.max(indicatorMinWidth, indicatorMinHeight) * clipRatio; 561 | indicatorMinRadius = indicatorClipRadius; 562 | indicatorMaxRadius = (indicatorMinRadius * radiusRatio); 563 | 564 | setElevation(view.getElevation() - 1f); 565 | setTranslationZ(view.getTranslationZ() - 1f); 566 | } 567 | 568 | private int getCalculatedOffsetY(ViewGroup parent) { 569 | Property property = new Property<>(parent.getTop()); 570 | 571 | getCalculatedOffsetY(parent.getParent(), property); 572 | 573 | return property.getValue(); 574 | } 575 | 576 | private int getCalculatedOffsetX(ViewGroup parent) { 577 | Property property = new Property<>(parent.getLeft()); 578 | 579 | getCalculatedOffsetX(parent.getParent(), property); 580 | 581 | return property.getValue(); 582 | } 583 | 584 | private void getCalculatedOffsetY(ViewParent parent, Property offset) { 585 | if (parent instanceof ViewGroup) { 586 | ViewGroup group = (ViewGroup) parent; 587 | offset.setValue(offset.getValue() + group.getTop()); 588 | if (group.getParent() != null) { 589 | getCalculatedOffsetY(group.getParent(), offset); 590 | } 591 | } 592 | } 593 | 594 | private void getCalculatedOffsetX(ViewParent parent, Property offset) { 595 | if (parent instanceof ViewGroup) { 596 | ViewGroup group = (ViewGroup) parent; 597 | offset.setValue(offset.getValue() + group.getLeft()); 598 | if (group.getParent() != null) { 599 | getCalculatedOffsetX(group.getParent(), offset); 600 | } 601 | } 602 | } 603 | 604 | public float getOffsetX() { 605 | return offsetX; 606 | } 607 | 608 | public void setOffsetX(float offsetX) { 609 | this.offsetX = offsetX; 610 | } 611 | 612 | public float getOffsetY() { 613 | return offsetY; 614 | } 615 | 616 | public void setOffsetY(float offsetY) { 617 | this.offsetY = offsetY; 618 | } 619 | 620 | public int getIndicatorStrokeColor() { 621 | return indicatorStrokeColor; 622 | } 623 | 624 | public void setIndicatorStrokeColor(int indicatorStrokeColor) { 625 | this.indicatorStrokeColor = indicatorStrokeColor; 626 | } 627 | 628 | public int getIndicatorColorStart() { 629 | return indicatorColorStart; 630 | } 631 | 632 | public void setIndicatorColorStart(int indicatorColorStart) { 633 | this.indicatorColorStart = indicatorColorStart; 634 | } 635 | 636 | public int getIndicatorInnerOutlineColor() { 637 | return indicatorInnerOutlineColor; 638 | } 639 | 640 | public void setIndicatorInnerOutlineColor(int indicatorInnerOutlineColor) { 641 | this.indicatorInnerOutlineColor = indicatorInnerOutlineColor; 642 | } 643 | 644 | public int getIndicatorColorEnd() { 645 | return indicatorColorEnd; 646 | } 647 | 648 | public void setIndicatorColorEnd(int indicatorColorEnd) { 649 | this.indicatorColorEnd = indicatorColorEnd; 650 | } 651 | 652 | public boolean isAnimationRunning() { 653 | return animationRunning; 654 | } 655 | 656 | public void setAnimationRunning(boolean animationRunning) { 657 | this.animationRunning = animationRunning; 658 | } 659 | 660 | public boolean isShowBorderStroke() { 661 | return showBorderStroke; 662 | } 663 | 664 | public void setShowBorderStroke(boolean showBorderStroke) { 665 | this.showBorderStroke = showBorderStroke; 666 | } 667 | 668 | public boolean isShowInnerOutline() { 669 | return showInnerOutline; 670 | } 671 | 672 | public void setShowInnerOutline(boolean showInnerOutline) { 673 | this.showInnerOutline = showInnerOutline; 674 | } 675 | 676 | public float getInnerOutLineWidth() { 677 | return innerOutLineWidth; 678 | } 679 | 680 | public void setInnerOutLineWidth(float innerOutLineWidth) { 681 | this.innerOutLineWidth = innerOutLineWidth; 682 | } 683 | 684 | public long getIndicatorIntervalDelay() { 685 | return indicatorIntervalDelay; 686 | } 687 | 688 | public void setIndicatorIntervalDelay(long indicatorIntervalDelay) { 689 | this.indicatorIntervalDelay = indicatorIntervalDelay; 690 | } 691 | 692 | public long getRevealDuration() { 693 | return revealDuration; 694 | } 695 | 696 | public void setRevealDuration(long revealDuration) { 697 | this.revealDuration = revealDuration; 698 | } 699 | 700 | public long getConcealDuration() { 701 | return concealDuration; 702 | } 703 | 704 | public void setConcealDuration(long concealDuration) { 705 | this.concealDuration = concealDuration; 706 | } 707 | 708 | public void setTarget(int centerX, int centerY) { 709 | this.centerX = centerX; 710 | this.centerY = centerY; 711 | } 712 | 713 | public void setDrawListener(ViewDrawListener listener) { 714 | this.listener = listener; 715 | } 716 | 717 | public void setCenterX(float centerX) { 718 | this.centerX = centerX; 719 | } 720 | 721 | public void setCenterY(float centerY) { 722 | this.centerY = centerY; 723 | } 724 | 725 | public float getIndicatorMinWidth() { 726 | return indicatorMinWidth; 727 | } 728 | 729 | public void setIndicatorMinWidth(float indicatorMinWidth) { 730 | this.indicatorMinWidth = indicatorMinWidth; 731 | } 732 | 733 | public float getIndicatorMinHeight() { 734 | return indicatorMinHeight; 735 | } 736 | 737 | public void setIndicatorMinHeight(float indicatorMinHeight) { 738 | this.indicatorMinHeight = indicatorMinHeight; 739 | } 740 | 741 | public float getIndicatorX() { 742 | return indicatorX; 743 | } 744 | 745 | public void setIndicatorX(float x) { 746 | this.indicatorX = x; 747 | } 748 | 749 | public float getIndicatorY() { 750 | return indicatorY; 751 | } 752 | 753 | public void setIndicatorY(float y) { 754 | this.indicatorY = y; 755 | } 756 | 757 | public float getIndicatorMaxWidth() { 758 | return indicatorMaxWidth; 759 | } 760 | 761 | public void setIndicatorMaxWidth(float indicatorMaxWidth) { 762 | this.indicatorMaxWidth = indicatorMaxWidth; 763 | } 764 | 765 | public float getIndicatorMaxHeight() { 766 | return indicatorMaxHeight; 767 | } 768 | 769 | public void setIndicatorMaxHeight(float indicatorMaxHeight) { 770 | this.indicatorMaxHeight = indicatorMaxHeight; 771 | } 772 | 773 | public boolean isUseColorInterpolation() { 774 | return useColorInterpolation; 775 | } 776 | 777 | public void setUseColorInterpolation(boolean useColorInterpolation) { 778 | this.useColorInterpolation = useColorInterpolation; 779 | } 780 | 781 | public Runnable getOnEnd() { 782 | return onEnd; 783 | } 784 | 785 | public void setOnEnd(Runnable onEnd) { 786 | this.onEnd = onEnd; 787 | } 788 | 789 | public Runnable getOnStart() { 790 | return onStart; 791 | } 792 | 793 | public void setOnStart(Runnable onStart) { 794 | this.onStart = onStart; 795 | } 796 | 797 | public boolean isCleanUpAfter() { 798 | return cleanUpAfter; 799 | } 800 | 801 | public void setCleanUpAfter(boolean cleanUpAfter) { 802 | this.cleanUpAfter = cleanUpAfter; 803 | } 804 | 805 | public int getIndicatorShape() { 806 | return indicatorShape; 807 | } 808 | 809 | public void setIndicatorShape(int indicatorShape) { 810 | this.indicatorShape = indicatorShape; 811 | } 812 | 813 | public float getIndicatorCornerRadius() { 814 | return indicatorCornerRadius; 815 | } 816 | 817 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 818 | this.indicatorCornerRadius = indicatorCornerRadius; 819 | } 820 | 821 | public float getIndicatorStrokeWidth() { 822 | return indicatorStrokeWidth; 823 | } 824 | 825 | public void setIndicatorStrokeWidth(float indicatorStrokeWidth) { 826 | this.indicatorStrokeWidth = indicatorStrokeWidth; 827 | } 828 | 829 | public int getIndicatorType() { 830 | return indicatorType; 831 | } 832 | 833 | public void setIndicatorType(int indicatorType) { 834 | this.indicatorType = indicatorType; 835 | } 836 | 837 | public int getIndicatorColor() { 838 | return indicatorColor; 839 | } 840 | 841 | public void setIndicatorColor(int indicatorColor) { 842 | this.indicatorColor = indicatorColor; 843 | this.color.setColor(indicatorColor); 844 | } 845 | 846 | public int getIndicatorRepeats() { 847 | return indicatorRepeats; 848 | } 849 | 850 | public void setIndicatorRepeats(int indicatorRepeats) { 851 | this.indicatorRepeats = indicatorRepeats; 852 | } 853 | 854 | public int getIndicatorRepeatMode() { 855 | return indicatorRepeatMode; 856 | } 857 | 858 | public void setIndicatorRepeatMode(int indicatorRepeatMode) { 859 | this.indicatorRepeatMode = indicatorRepeatMode; 860 | } 861 | 862 | public long getIndicatorDuration() { 863 | return indicatorDuration; 864 | } 865 | 866 | public void setIndicatorDuration(long indicatorDuration) { 867 | this.indicatorDuration = indicatorDuration; 868 | } 869 | 870 | public float getIndicatorMinOpacity() { 871 | return indicatorMinOpacity; 872 | } 873 | 874 | public void setIndicatorMinOpacity(float indicatorMinOpacity) { 875 | this.indicatorMinOpacity = indicatorMinOpacity; 876 | } 877 | 878 | public float getIndicatorMaxOpacity() { 879 | return indicatorMaxOpacity; 880 | } 881 | 882 | public void setIndicatorMaxOpacity(float indicatorMaxOpacity) { 883 | this.indicatorMaxOpacity = indicatorMaxOpacity; 884 | } 885 | 886 | public float getIndicatorClipRadius() { 887 | return indicatorClipRadius; 888 | } 889 | 890 | public void setIndicatorClipRadius(float indicatorClipRadius) { 891 | this.indicatorClipRadius = indicatorClipRadius; 892 | } 893 | 894 | public float getIndicatorMaxRadius() { 895 | return indicatorMaxRadius; 896 | } 897 | 898 | public void setIndicatorMaxRadius(float indicatorMaxRadius) { 899 | this.indicatorMaxRadius = indicatorMaxRadius; 900 | } 901 | 902 | public float getIndicatorMinRadius() { 903 | return indicatorMinRadius; 904 | } 905 | 906 | public void setIndicatorMinRadius(float indicatorMinRadius) { 907 | this.indicatorMinRadius = indicatorMinRadius; 908 | } 909 | 910 | public int getIndicatorCount() { 911 | return indicatorCount; 912 | } 913 | 914 | public void setIndicatorCount(int indicatorCount) { 915 | if (indicatorCount == this.indicatorCount) 916 | return; 917 | 918 | this.indicatorCount = indicatorCount; 919 | } 920 | 921 | public IndicatorLayoutBehaviour getBehaviour() { 922 | return behaviour; 923 | } 924 | 925 | public void setBehaviour(IndicatorLayoutBehaviour behaviour) { 926 | this.behaviour = behaviour; 927 | } 928 | 929 | public int getUsableWidth() { 930 | return usableWidth; 931 | } 932 | 933 | public int getUsableHeight() { 934 | return usableHeight; 935 | } 936 | 937 | public float getCenterX() { 938 | return centerX; 939 | } 940 | 941 | public void setCenterX(int centerX) { 942 | this.centerX = centerX; 943 | } 944 | 945 | public float getCenterY() { 946 | return centerY; 947 | } 948 | 949 | public void setCenterY(int centerY) { 950 | this.centerY = centerY; 951 | } 952 | 953 | public Bounds getBounds() { 954 | return bounds; 955 | } 956 | 957 | public interface ViewDrawListener { 958 | void onViewDrawn(View view); 959 | } 960 | } -------------------------------------------------------------------------------- /indicatoreffectlib/src/main/res/values/attrs.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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /indicatoreffectlib/src/test/java/com/eudycontreras/indicatoreffectlib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.eudycontreras.indicatoreffectlib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 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() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':indicatoreffectlib' 2 | --------------------------------------------------------------------------------