├── fluid-slider
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── attrs.xml
│ │ └── kotlin
│ │ └── com
│ │ └── ramotion
│ │ └── fluidslider
│ │ └── FluidSlider.kt
├── proguard-rules.pro
└── build.gradle
├── fluid-slider-example-simple
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── 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
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── com
│ │ └── ramotion
│ │ └── fluidslider
│ │ └── example
│ │ └── simple
│ │ └── MainActivity.kt
├── proguard-rules.pro
└── build.gradle
├── fluid-slider-example-simple-java
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── 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
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── ramotion
│ │ └── fluidslider
│ │ └── example
│ │ └── simple
│ │ └── java
│ │ └── MainActivity.java
├── proguard-rules.pro
└── build.gradle
├── header.png
├── Fluid_slider.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── gradle.properties
├── .gitignore
├── LICENSE
├── gradlew.bat
├── gradlew
└── README.md
/fluid-slider/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/header.png
--------------------------------------------------------------------------------
/fluid-slider/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Fluid_slider.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/Fluid_slider.gif
--------------------------------------------------------------------------------
/fluid-slider/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | fluid-slider
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':fluid-slider-example-simple',
2 | ':fluid-slider',
3 | ':fluid-slider-example-simple-java'
4 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ramotion/fluid-slider-android/HEAD/fluid-slider-example-simple-java/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Mar 03 10:12:16 MSK 2020
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-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | fluid-slider-android
3 | Kotlin is a great fit for developing Android applications, bringing all of the advantages of a modern language to the Android platform without introducing any new restrictions.
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | fluid-slider-example-simple-java
3 | Kotlin is a great fit for developing Android applications, bringing all of the advantages of a modern language to the Android platform without introducing any new restrictions.
4 |
5 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fluid-slider/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 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/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 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | android.useAndroidX=true
11 | android.enableJetifier=true
12 |
13 | # Specifies the JVM arguments used for the daemon process.
14 | # The setting is particularly useful for tweaking memory settings.
15 | org.gradle.jvmargs=-Xmx1536m
16 |
17 | # When configured, Gradle will run in incubating parallel mode.
18 | # This option should only be used with decoupled projects. More details, visit
19 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
20 | # org.gradle.parallel=true
21 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/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 |
--------------------------------------------------------------------------------
/fluid-slider/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Java compiled classes #
2 | # Package Files #
3 | # IDEA Project files #
4 | *.iws
5 | *.iml
6 | *.ipr
7 | .idea/
8 |
9 | # Build directory #
10 | target/
11 | build/
12 |
13 | # Generated files #
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Local props file #
19 | local.properties
20 |
21 | # Gradle cache #
22 | .gradle
23 |
24 | # OSX files #
25 | .DS_Store
26 |
27 | # NDK #
28 | # files for the dex VM #
29 | # Proguard folder generated by Eclipse
30 | proguard/
31 |
32 | # Log Files
33 | *.log
34 |
35 | # Android Studio Navigation editor temp files
36 | .navigation/
37 |
38 | # Android Studio captures folder
39 | captures/
40 |
41 | # Keystore files
42 | *.jks
43 |
44 | # External native build folder generated in Android Studio 2.2 and later
45 | .externalNativeBuild
46 |
47 | # Google Services (e.g. APIs or Firebase)
48 | google-services.json
49 |
50 | # Freeline
51 | freeline.py
52 | freeline/
53 | freeline_project_description.json
54 | fluid-slider-simple-example/libs/
55 | fluid-slider/libs/
56 | fluid-slider/src/main/java/
57 | fluid-slider/src/main/res/drawable/
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ramotion
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 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/kotlin/com/ramotion/fluidslider/example/simple/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ramotion.fluidslider.example.simple
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import android.widget.TextView
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.ramotion.fluidslider.FluidSlider
8 |
9 | class MainActivity : AppCompatActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_main)
14 |
15 | val textView = findViewById(R.id.textView)
16 |
17 | val max = 45
18 | val min = 10
19 | val total = max - min
20 |
21 | val slider = findViewById(R.id.fluidSlider)
22 | slider.positionListener = { pos -> slider.bubbleText = "${min + (total * pos).toInt()}" }
23 | slider.position = 0.3f
24 | slider.startText ="$min"
25 | slider.endText = "$max"
26 |
27 | slider.beginTrackingListener = { textView.visibility = View.INVISIBLE }
28 | slider.endTrackingListener = { textView.visibility = View.VISIBLE }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 29
6 |
7 | defaultConfig {
8 | applicationId "com.ramotion.fluidslider.example.simple.java"
9 | minSdkVersion 19
10 | targetSdkVersion 29
11 | versionCode 2
12 | versionName "1.1"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(dir: 'libs', include: ['*.jar'])
33 | implementation project(':fluid-slider')
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
35 | implementation 'androidx.appcompat:appcompat:1.1.0'
36 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
37 | testImplementation 'junit:junit:4.12'
38 | androidTestImplementation 'androidx.test:runner:1.2.0'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
40 | }
--------------------------------------------------------------------------------
/fluid-slider-example-simple/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 |
8 | defaultConfig {
9 | applicationId "com.ramotion.fluidslider.example.simple"
10 | minSdkVersion 16
11 | targetSdkVersion 29
12 | versionCode 2
13 | versionName "1.1"
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | sourceSets {
25 | main.java.srcDirs += 'src/main/kotlin'
26 | }
27 |
28 | buildToolsVersion '28.0.3'
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(include: ['*.jar'], dir: 'libs')
33 | implementation project(':fluid-slider')
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
35 | implementation 'androidx.appcompat:appcompat:1.1.0'
36 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
37 |
38 | testImplementation 'junit:junit:4.12'
39 | androidTestImplementation 'androidx.test:runner:1.2.0'
40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
41 | }
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
23 |
24 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/java/com/ramotion/fluidslider/example/simple/java/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ramotion.fluidslider.example.simple.java;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import com.ramotion.fluidslider.FluidSlider;
8 |
9 | import androidx.appcompat.app.AppCompatActivity;
10 | import kotlin.Unit;
11 | import kotlin.jvm.functions.Function0;
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | @SuppressWarnings("Convert2Lambda")
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 |
21 | final TextView textView = findViewById(R.id.textView);
22 |
23 | final int max = 45;
24 | final int min = 10;
25 | final int total = max - min;
26 |
27 | final FluidSlider slider = findViewById(R.id.fluidSlider);
28 | slider.setBeginTrackingListener(new Function0() {
29 | @Override
30 | public Unit invoke() {
31 | textView.setVisibility(View.INVISIBLE);
32 | return Unit.INSTANCE;
33 | }
34 | });
35 |
36 | slider.setEndTrackingListener(new Function0() {
37 | @Override
38 | public Unit invoke() {
39 | textView.setVisibility(View.VISIBLE);
40 | return Unit.INSTANCE;
41 | }
42 | });
43 |
44 | // Java 8 lambda
45 | slider.setPositionListener(pos -> {
46 | final String value = String.valueOf( (int)(min + total * pos) );
47 | slider.setBubbleText(value);
48 | return Unit.INSTANCE;
49 | });
50 |
51 |
52 | slider.setPosition(0.3f);
53 | slider.setStartText(String.valueOf(min));
54 | slider.setEndText(String.valueOf(max));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/fluid-slider/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'signing'
4 | apply plugin: 'com.bmuschko.nexus'
5 |
6 | group = 'com.ramotion.fluidslider'
7 | version = '0.3.1'
8 |
9 | android {
10 | compileSdkVersion 29
11 | defaultConfig {
12 | minSdkVersion 16
13 | targetSdkVersion 29
14 | versionCode 5
15 | versionName version
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 |
19 | }
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | sourceSets {
27 | main.java.srcDirs += 'src/main/kotlin'
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(include: ['*.jar'], dir: 'libs')
33 | implementation 'androidx.appcompat:appcompat:1.1.0'
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
35 |
36 | testImplementation 'junit:junit:4.12'
37 | androidTestImplementation 'androidx.test:runner:1.2.0'
38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
39 | }
40 |
41 | repositories {
42 | mavenCentral()
43 | }
44 |
45 | modifyPom {
46 | project {
47 | name 'Fluid Slider for Android'
48 | description 'A slider widget with a popup bubble displaying the precise value selected. http://ramotion.com'
49 | url 'https://github.com/Ramotion/fluid-slider-android'
50 | inceptionYear '2017'
51 |
52 | scm {
53 | url 'https://github.com/Ramotion/fluid-slider-android'
54 | connection 'scm:git@github.com:Ramotion/fluid-slider-android.git'
55 | developerConnection 'scm:git@github.com:Ramotion/fluid-slider-android.git'
56 | }
57 |
58 | licenses {
59 | license {
60 | name 'The MIT License (MIT)'
61 | url 'https://opensource.org/licenses/mit-license.php'
62 | distribution 'repo'
63 | }
64 | }
65 |
66 | developers {
67 | developer {
68 | id 'dvg4000'
69 | name 'Dmitry Grishechkin'
70 | email 'dvgrishechkin@yandex.ru'
71 | }
72 | }
73 | }
74 | }
75 |
76 | nexus {
77 | sign = true
78 | repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
79 | snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
80 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/fluid-slider-example-simple-java/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | FLUID SLIDER [KOTLIN]
7 |
8 | A slider widget with a popup bubble displaying the precise value selected
9 |
10 |
11 | ___
12 |
13 |
14 | We specialize in the designing and coding of custom UI for Mobile Apps and Websites.
15 |
16 |
17 |
18 | Stay tuned for the latest updates:
19 |
20 |
21 |
22 | Inspired by [Virgil Pana](https://dribbble.com/virgilpana) [shot](https://dribbble.com/shots/3868232-Fluid-Slider)
23 |
24 |
25 |
26 | [](http://twitter.com/Ramotion)
27 | [](https://www.codacy.com/app/dvg4000/fluid-slider-android?utm_source=github.com&utm_medium=referral&utm_content=Ramotion/fluid-slider-android&utm_campaign=Badge_Grade)
28 | [](https://paypal.me/Ramotion)
29 |
30 | ## Requirements
31 |
32 | - Android 4.1 Jelly Bean (API lvl 16) or greater
33 | - Your favorite IDE
34 |
35 | ## Installation
36 |
37 | Just download the package from [here](http://central.maven.org/maven2/com/ramotion/fluidslider/fluid-slider/0.3.1/fluid-slider-0.3.1.aar) and add it to your project classpath, or just use the maven repo:
38 |
39 | Gradle:
40 | ```groovy
41 | implementation 'com.ramotion.fluidslider:fluid-slider:0.3.1'
42 | ```
43 | SBT:
44 | ```scala
45 | libraryDependencies += "com.ramotion.fluidslider" % "fluid-slider" % "0.3.1"
46 | ```
47 | Maven:
48 | ```xml
49 |
50 | com.ramotion.fluidslider
51 | fluid-slider
52 | 0.3.1
53 | aar
54 |
55 | ```
56 |
57 | ## Basic usage
58 |
59 | Place the `FluidSlider` in your layout.
60 |
61 | To track the current position of the slider, set the `positionListener`, as shown below:
62 | ```
63 | val slider = findViewById(R.id.fluidSlider)
64 | slider.positionListener = { p -> Log.d("MainActivity", "current position is: $p" )}
65 | ```
66 |
67 | You can also track the beginning and completion of the movement of the slider, using the following properties:
68 | `beginTrackingListener` and` endTrackingListener`. Example below:
69 | ```
70 | slider.beginTrackingListener = { /* action on slider touched */ }
71 | slider.endTrackingListener = { /* action on slider released */ }
72 | ```
73 |
74 | Here is simple example, how to change `FluidSlider` range.
75 | ```kotlin
76 | // Kotlin
77 | val max = 45
78 | val min = 10
79 | val total = max - min
80 |
81 | val slider = findViewById(R.id.fluidSlider)
82 | slider.positionListener = { pos -> slider.bubbleText = "${min + (total * pos).toInt()}" }
83 | slider.position = 0.3f
84 | slider.startText ="$min"
85 | slider.endText = "$max"
86 |
87 | // Java
88 | final FluidSlider slider = findViewById(R.id.fluidSlider);
89 | slider.setBeginTrackingListener(new Function0() {
90 | @Override
91 | public Unit invoke() {
92 | Log.d("D", "setBeginTrackingListener");
93 | return Unit.INSTANCE;
94 | }
95 | });
96 |
97 | slider.setEndTrackingListener(new Function0() {
98 | @Override
99 | public Unit invoke() {
100 | Log.d("D", "setEndTrackingListener");
101 | return Unit.INSTANCE;
102 | }
103 | });
104 |
105 | // Or Java 8 lambda
106 | slider.setPositionListener(pos -> {
107 | final String value = String.valueOf( (int)((1 - pos) * 100) );
108 | slider.setBubbleText(value);
109 | return Unit.INSTANCE;
110 | });
111 | ```
112 |
113 | Here are the attributes you can specify through XML or related setters:
114 | * `bar_color` - Color of slider.
115 | * `bubble_color` - Color of circle "bubble" inside bar.
116 | * `bar_text_color` - Color of `start` and `end` texts of slider.
117 | * `bubble_text_color` - Color of text inside "bubble".
118 | * `start_text` - Start (left) text of slider.
119 | * `end_text` - End (right) text of slider.
120 | * `text_size` - Text size.
121 | * `duration` - Duration of "bubble" rise in milliseconds.
122 | * `initial_position` - Initial positon of "bubble" in range form `0.0` to `1.0`.
123 | * `size` - Height of slider. Can be `small` (40dp) and `normal` (56dp).
124 |
125 |
126 | This library is a part of a selection of our best UI open-source projects.
127 |
128 | ## Third Party Bindings
129 | ### React Native
130 | You may now use this library with [React Native](https://github.com/facebook/react-native) via the module [here](https://github.com/prscX/react-native-fluidic-slider)
131 |
132 | ## 🗂 Check this library on other language:
133 |
134 |
135 |
136 |
137 | ## 📄 License
138 |
139 | Fluid Slider Android is released under the MIT license.
140 | See [LICENSE](./LICENSE) for details.
141 |
142 | This library is a part of a selection of our best UI open-source projects
143 |
144 | If you use the open-source library in your project, please make sure to credit and backlink to www.ramotion.com
145 |
146 | ## 📱 Get the Showroom App for Android to give it a try
147 | Try this UI component and more like this in our Android app. Contact us if interested.
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/fluid-slider/src/main/kotlin/com/ramotion/fluidslider/FluidSlider.kt:
--------------------------------------------------------------------------------
1 | package com.ramotion.fluidslider
2 |
3 | import android.animation.ValueAnimator
4 | import android.annotation.SuppressLint
5 | import android.content.Context
6 | import android.graphics.*
7 | import android.os.Build
8 | import android.os.Parcel
9 | import android.os.Parcelable
10 | import android.util.AttributeSet
11 | import android.view.MotionEvent
12 | import android.view.View
13 | import android.view.ViewOutlineProvider
14 | import android.view.animation.OvershootInterpolator
15 | import com.ramotion.fluidslider.FluidSlider.Size.NORMAL
16 | import com.ramotion.fluidslider.FluidSlider.Size.SMALL
17 | import kotlin.math.*
18 |
19 |
20 | class FluidSlider @JvmOverloads constructor(
21 | context: Context,
22 | attrs: AttributeSet? = null,
23 | defStyleAttr: Int = 0,
24 | size: Size = Size.NORMAL) : View(context, attrs, defStyleAttr) {
25 |
26 | /**
27 | * Sizes that can be used.
28 | * @see NORMAL
29 | * @see SMALL
30 | */
31 | enum class Size(val value: Int) {
32 | /**
33 | * Default size - 56dp.
34 | */
35 | NORMAL(56),
36 |
37 | /**
38 | * Small size - 40dp.
39 | */
40 | SMALL(40)
41 | }
42 |
43 | private companion object {
44 | const val BAR_CORNER_RADIUS = 2
45 | const val BAR_VERTICAL_OFFSET = 1.5f
46 | const val BAR_INNER_HORIZONTAL_OFFSET = 0
47 |
48 | const val SLIDER_WIDTH = 4
49 | const val SLIDER_HEIGHT = 1 + BAR_VERTICAL_OFFSET
50 |
51 | const val TOP_CIRCLE_DIAMETER = 1
52 | const val BOTTOM_CIRCLE_DIAMETER = 25.0f
53 | const val TOUCH_CIRCLE_DIAMETER = 1
54 | const val LABEL_CIRCLE_DIAMETER = 10
55 |
56 | const val ANIMATION_DURATION = 400
57 | const val TOP_SPREAD_FACTOR = 0.4f
58 | const val BOTTOM_START_SPREAD_FACTOR = 0.25f
59 | const val BOTTOM_END_SPREAD_FACTOR = 0.1f
60 | const val METABALL_HANDLER_FACTOR = 2.4f
61 | const val METABALL_MAX_DISTANCE = 15.0f
62 | const val METABALL_RISE_DISTANCE = 1.1f
63 |
64 | const val TEXT_SIZE = 12
65 | const val TEXT_OFFSET = 8
66 | const val TEXT_START = "0"
67 | const val TEXT_END = "100"
68 |
69 | const val COLOR_BAR = 0xff6168e7.toInt()
70 | const val COLOR_LABEL = Color.WHITE
71 | const val COLOR_LABEL_TEXT = Color.BLACK
72 | const val COLOR_BAR_TEXT = Color.WHITE
73 |
74 | const val INITIAL_POSITION = 0.5f
75 | }
76 |
77 | private val barHeight: Float
78 |
79 | private val desiredWidth: Int
80 | private val desiredHeight: Int
81 |
82 | private val topCircleDiameter: Float
83 | private val bottomCircleDiameter: Float
84 | private val touchRectDiameter: Float
85 | private val labelRectDiameter: Float
86 |
87 | private val metaballMaxDistance: Float
88 | private val metaballRiseDistance: Float
89 | private val textOffset: Float
90 |
91 | private val barVerticalOffset: Float
92 | private val barCornerRadius: Float
93 | private val barInnerOffset: Float
94 |
95 | private val rectBar = RectF()
96 | private val rectTopCircle = RectF()
97 | private val rectBottomCircle = RectF()
98 | private val rectTouch = RectF()
99 | private val rectLabel = RectF()
100 | private val rectText = Rect()
101 | private val pathMetaball = Path()
102 |
103 | private val paintBar: Paint
104 | private val paintLabel: Paint
105 | private val paintText: Paint
106 |
107 | private var maxMovement = 0f
108 | private var touchX: Float? = null
109 |
110 | /**
111 | * Duration of "bubble" rise in milliseconds.
112 | */
113 | var duration = ANIMATION_DURATION.toLong()
114 | set(value) {
115 | field = abs(value)
116 | }
117 |
118 | /**
119 | * Color of text inside "bubble".
120 | */
121 | @Suppress("MemberVisibilityCanBePrivate")
122 | var colorBubbleText = COLOR_LABEL_TEXT
123 |
124 | /**
125 | * Color of `start` and `end` texts of slider.
126 | */
127 | @Suppress("MemberVisibilityCanBePrivate")
128 | var colorBarText = COLOR_BAR_TEXT
129 |
130 | /**
131 | * Color of slider.
132 | */
133 | @Suppress("MemberVisibilityCanBePrivate")
134 | var colorBar: Int
135 | get() = paintBar.color
136 | set(value) {
137 | paintBar.color = value
138 | }
139 |
140 | /**
141 | * Color of circle "bubble" inside bar.
142 | */
143 | @Suppress("MemberVisibilityCanBePrivate")
144 | var colorBubble: Int
145 | get() = paintLabel.color
146 | set(value) {
147 | paintLabel.color = value
148 | }
149 |
150 | /**
151 | * Text size.
152 | */
153 | var textSize: Float
154 | get() = paintText.textSize
155 | set(value) {
156 | paintText.textSize = value
157 | }
158 |
159 | /**
160 | * Bubble text.
161 | */
162 | var bubbleText: String? = null
163 |
164 | /**
165 | * Start (left) text of slider.
166 | */
167 | var startText: String? = TEXT_START
168 |
169 | /**
170 | * End (right) text of slider.
171 | */
172 | var endText: String? = TEXT_END
173 |
174 | /**
175 | * Initial position of "bubble" in range form `0.0` to `1.0`.
176 | */
177 | var position = INITIAL_POSITION
178 | set(value) {
179 | field = max(0f, min(1f, value))
180 | invalidate()
181 | positionListener?.invoke(field)
182 | }
183 |
184 | /**
185 | * Current position tracker. Receive current position, in range from `0.0f` to `1.0f`.
186 | */
187 | var positionListener: ((Float) -> Unit)? = null
188 |
189 | /**
190 | * Called on slider touch.
191 | */
192 | var beginTrackingListener: (() -> Unit)? = null
193 |
194 | /**
195 | * Called when slider is released.
196 | */
197 | var endTrackingListener: (() -> Unit)? = null
198 |
199 | @SuppressLint("NewApi")
200 | inner class OutlineProvider : ViewOutlineProvider() {
201 | override fun getOutline(v: View?, outline: Outline?) {
202 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
203 | val rect = Rect(rectBar.left.toInt(), rectBar.top.toInt(), rectBar.right.toInt(), rectBar.bottom.toInt())
204 | outline?.setRoundRect(rect, barCornerRadius)
205 | }
206 | }
207 | }
208 |
209 | class State : BaseSavedState {
210 | companion object {
211 | @JvmField
212 | @Suppress("unused")
213 | val CREATOR = object : Parcelable.Creator {
214 | override fun createFromParcel(parcel: Parcel): State = State(parcel)
215 | override fun newArray(size: Int): Array = arrayOfNulls(size)
216 | }
217 | }
218 |
219 | val position: Float
220 | val startText: String?
221 | val endText: String?
222 | val textSize: Float
223 | val colorLabel: Int
224 | val colorBar: Int
225 | val colorBarText: Int
226 | val colorLabelText: Int
227 | val duration: Long
228 |
229 | constructor(superState: Parcelable?,
230 | position: Float,
231 | startText: String?,
232 | endText: String?,
233 | textSize: Float,
234 | colorLabel: Int,
235 | colorBar: Int,
236 | colorBarText: Int,
237 | colorLabelText: Int,
238 | duration: Long) : super(superState) {
239 | this.position = position
240 | this.startText = startText
241 | this.endText = endText
242 | this.textSize = textSize
243 | this.colorLabel = colorLabel
244 | this.colorBar = colorBar
245 | this.colorBarText = colorBarText
246 | this.colorLabelText = colorLabelText
247 | this.duration = duration
248 | }
249 |
250 | private constructor(parcel: Parcel) : super(parcel) {
251 | this.position = parcel.readFloat()
252 | this.startText = parcel.readString()
253 | this.endText = parcel.readString()
254 | this.textSize = parcel.readFloat()
255 | this.colorLabel = parcel.readInt()
256 | this.colorBar = parcel.readInt()
257 | this.colorBarText = parcel.readInt()
258 | this.colorLabelText = parcel.readInt()
259 | this.duration = parcel.readLong()
260 | }
261 |
262 | override fun writeToParcel(parcel: Parcel, i: Int) {
263 | super.writeToParcel(parcel, i)
264 | parcel.writeFloat(position)
265 | parcel.writeString(startText)
266 | parcel.writeString(endText)
267 | parcel.writeFloat(textSize)
268 | parcel.writeInt(colorLabel)
269 | parcel.writeInt(colorBar)
270 | parcel.writeInt(colorBarText)
271 | parcel.writeInt(colorLabelText)
272 | parcel.writeLong(duration)
273 | }
274 |
275 | override fun describeContents(): Int = 0
276 | }
277 |
278 | init {
279 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
280 | outlineProvider = OutlineProvider()
281 | }
282 |
283 | paintBar = Paint(Paint.ANTI_ALIAS_FLAG)
284 | paintBar.style = Paint.Style.FILL
285 |
286 | paintLabel = Paint(Paint.ANTI_ALIAS_FLAG)
287 | paintLabel.style = Paint.Style.FILL
288 |
289 | paintText = Paint(Paint.ANTI_ALIAS_FLAG)
290 |
291 | val density = context.resources.displayMetrics.density
292 |
293 | if (attrs != null) {
294 | val a = context.theme.obtainStyledAttributes(attrs, R.styleable.FluidSlider, defStyleAttr, 0)
295 | try {
296 | colorBar = a.getColor(R.styleable.FluidSlider_bar_color, COLOR_BAR)
297 | colorBubble = a.getColor(R.styleable.FluidSlider_bubble_color, COLOR_LABEL)
298 | colorBarText = a.getColor(R.styleable.FluidSlider_bar_text_color, COLOR_BAR_TEXT)
299 | colorBubbleText = a.getColor(R.styleable.FluidSlider_bubble_text_color, COLOR_LABEL_TEXT)
300 |
301 | position = max(0f, min(1f, a.getFloat(R.styleable.FluidSlider_initial_position, INITIAL_POSITION)))
302 | textSize = a.getDimension(R.styleable.FluidSlider_text_size, TEXT_SIZE * density)
303 | duration = abs(a.getInteger(R.styleable.FluidSlider_duration, ANIMATION_DURATION)).toLong()
304 |
305 | a.getString(R.styleable.FluidSlider_start_text)?.also { startText = it }
306 | a.getString(R.styleable.FluidSlider_end_text)?.also { endText = it }
307 |
308 | val defaultBarHeight = if (a.getInteger(R.styleable.FluidSlider_size, 1) == 1) Size.NORMAL.value else Size.SMALL.value
309 | barHeight = defaultBarHeight * density
310 | } finally {
311 | a.recycle()
312 | }
313 | } else {
314 | colorBar = COLOR_BAR
315 | colorBubble = COLOR_LABEL
316 | textSize = TEXT_SIZE * density
317 | barHeight = size.value * density
318 | }
319 |
320 | desiredWidth = (barHeight * SLIDER_WIDTH).toInt()
321 | desiredHeight = (barHeight * SLIDER_HEIGHT).toInt()
322 |
323 | topCircleDiameter = barHeight * TOP_CIRCLE_DIAMETER
324 | bottomCircleDiameter = barHeight * BOTTOM_CIRCLE_DIAMETER
325 | touchRectDiameter = barHeight * TOUCH_CIRCLE_DIAMETER
326 | labelRectDiameter = barHeight - LABEL_CIRCLE_DIAMETER * density
327 |
328 | metaballMaxDistance = barHeight * METABALL_MAX_DISTANCE
329 | metaballRiseDistance = barHeight * METABALL_RISE_DISTANCE
330 |
331 | barVerticalOffset = barHeight * BAR_VERTICAL_OFFSET
332 | barCornerRadius = BAR_CORNER_RADIUS * density
333 | barInnerOffset = BAR_INNER_HORIZONTAL_OFFSET * density
334 | textOffset = TEXT_OFFSET * density
335 | }
336 |
337 | /**
338 | * Additional constructor that can be used to create FluidSlider programmatically.
339 | * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
340 | * @param size Size of FluidSlider.
341 | * @see Size
342 | */
343 | constructor(context: Context, size: Size) : this(context, null, 0, size)
344 |
345 | override fun onSaveInstanceState(): Parcelable {
346 | return State(super.onSaveInstanceState(),
347 | position, startText, endText, textSize,
348 | colorBubble, colorBar, colorBarText, colorBubbleText, duration)
349 | }
350 |
351 | override fun onRestoreInstanceState(state: Parcelable) {
352 | if (state is State) {
353 | super.onRestoreInstanceState(state.superState)
354 | position = state.position
355 | startText = state.startText
356 | endText = state.endText
357 | textSize = state.textSize
358 | colorBubble = state.colorLabel
359 | colorBar = state.colorBar
360 | colorBarText = state.colorBarText
361 | colorBubbleText = state.colorLabelText
362 | duration = state.duration
363 | } else {
364 | super.onRestoreInstanceState(state)
365 | }
366 | }
367 |
368 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
369 | val w = resolveSizeAndState(desiredWidth, widthMeasureSpec, 0)
370 | val h = resolveSizeAndState(desiredHeight, heightMeasureSpec, 0)
371 | setMeasuredDimension(w, h)
372 | }
373 |
374 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
375 | super.onSizeChanged(w, h, oldw, oldh)
376 |
377 | val width = w.toFloat()
378 | rectBar.set(0f, barVerticalOffset, width, barVerticalOffset + barHeight)
379 | rectTopCircle.set(0f, barVerticalOffset, topCircleDiameter, barVerticalOffset + topCircleDiameter)
380 | rectBottomCircle.set(0f, barVerticalOffset, bottomCircleDiameter, barVerticalOffset + bottomCircleDiameter)
381 | rectTouch.set(0f, barVerticalOffset, touchRectDiameter, barVerticalOffset + touchRectDiameter)
382 |
383 | val vOffset = barVerticalOffset + (topCircleDiameter - labelRectDiameter) / 2f
384 | rectLabel.set(0f, vOffset, labelRectDiameter, vOffset + labelRectDiameter)
385 |
386 | maxMovement = width - touchRectDiameter - barInnerOffset * 2
387 | }
388 |
389 | override fun onDraw(canvas: Canvas) {
390 | super.onDraw(canvas)
391 |
392 | // Draw slider bar and text
393 | canvas.drawRoundRect(rectBar, barCornerRadius, barCornerRadius, paintBar)
394 |
395 | startText?.let { drawText(canvas, paintText, it, Paint.Align.LEFT, colorBarText, textOffset, rectBar, rectText) }
396 | endText?.let { drawText(canvas, paintText, it, Paint.Align.RIGHT, colorBarText, textOffset, rectBar, rectText) }
397 |
398 | // Draw metaball
399 | val x = barInnerOffset + touchRectDiameter / 2 + maxMovement * position
400 | offsetRectToPosition(x, rectTouch, rectTopCircle, rectBottomCircle, rectLabel)
401 |
402 | drawMetaball(canvas, paintBar, pathMetaball, rectBottomCircle, rectTopCircle, rectBar.top)
403 |
404 | // Draw label and text
405 | canvas.drawOval(rectLabel, paintLabel)
406 |
407 | val text = bubbleText ?: (position * 100).toInt().toString()
408 | drawText(canvas, paintText, text, Paint.Align.CENTER, colorBubbleText, 0f, rectLabel, rectText)
409 | }
410 |
411 | override fun performClick(): Boolean {
412 | super.performClick()
413 | return true
414 | }
415 |
416 | override fun onTouchEvent(event: MotionEvent): Boolean = when (event.actionMasked) {
417 | MotionEvent.ACTION_DOWN -> {
418 | val x = event.x
419 | val y = event.y
420 | if (rectBar.contains(x, y)) {
421 | if (!rectTouch.contains(x, y)) {
422 | position = max(0f, min(1f, (x - rectTouch.width() / 2) / maxMovement))
423 | }
424 | touchX = x
425 | beginTrackingListener?.invoke()
426 | showLabel(metaballRiseDistance)
427 | parent.requestDisallowInterceptTouchEvent(true)
428 | true
429 | } else {
430 | false
431 | }
432 | }
433 | MotionEvent.ACTION_MOVE -> {
434 | touchX?.let {
435 | touchX = event.x
436 | val newPos = max(0f, min(1f, position + (event.x - it) / maxMovement))
437 | position = newPos
438 | true
439 | } == true
440 | }
441 | MotionEvent.ACTION_UP,
442 | MotionEvent.ACTION_CANCEL -> {
443 | touchX?.let {
444 | touchX = null
445 | endTrackingListener?.invoke()
446 | hideLabel()
447 | performClick()
448 | parent.requestDisallowInterceptTouchEvent(false)
449 | true
450 | } == true
451 | }
452 | else -> false
453 | }
454 |
455 | private fun offsetRectToPosition(position: Float, vararg rects: RectF) {
456 | for (rect in rects) {
457 | rect.offsetTo(position - rect.width() / 2f, rect.top)
458 | }
459 | }
460 |
461 | private fun getVector(radians: Float, length: Float): Pair {
462 | val x = (cos(radians) * length)
463 | val y = (sin(radians) * length)
464 | return x to y
465 | }
466 |
467 | private fun getVectorLength(x1: Float, y1: Float, x2: Float, y2: Float): Float {
468 | val x = x1 - x2
469 | val y = y1 - y2
470 | return sqrt(x * x + y * y)
471 | }
472 |
473 | private fun drawMetaball(canvas: Canvas,
474 | paint: Paint,
475 | path: Path,
476 | circle1: RectF,
477 | circle2: RectF,
478 | topBorder: Float,
479 | riseDistance: Float = metaballRiseDistance,
480 | maxDistance: Float = metaballMaxDistance,
481 | cornerRadius: Float = barCornerRadius,
482 | topSpreadFactor: Float = TOP_SPREAD_FACTOR,
483 | bottomStartSpreadFactor: Float = BOTTOM_START_SPREAD_FACTOR,
484 | bottomEndSpreadFactor: Float = BOTTOM_END_SPREAD_FACTOR,
485 | handleRate: Float = METABALL_HANDLER_FACTOR) {
486 | val radius1 = circle1.width() / 2.0f
487 | val radius2 = circle2.width() / 2.0f
488 |
489 | if (radius1 == 0.0f || radius2 == 0.0f) {
490 | return
491 | }
492 |
493 | val d = getVectorLength(circle1.centerX(), circle1.centerY(), circle2.centerX(), circle2.centerY())
494 | if (d > maxDistance || d <= abs(radius1 - radius2)) {
495 | return
496 | }
497 |
498 | val riseRatio = min(1f, max(0f, topBorder - circle2.top) / riseDistance)
499 |
500 | val u1: Float
501 | val u2: Float
502 | if (d < radius1 + radius2) { // case circles are overlapping
503 | u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d))
504 | u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d))
505 | } else {
506 | u1 = 0.0f
507 | u2 = 0.0f
508 | }
509 |
510 | val centerXMin = circle2.centerX() - circle1.centerX()
511 | val centerYMin = circle2.centerY() - circle1.centerY()
512 |
513 | val bottomSpreadDiff = bottomStartSpreadFactor - bottomEndSpreadFactor
514 | val bottomSpreadFactor = bottomStartSpreadFactor - bottomSpreadDiff * riseRatio
515 |
516 | val fPI = PI.toFloat()
517 | val angle1 = atan2(centerYMin, centerXMin)
518 | val angle2 = acos((radius1 - radius2) / d)
519 | val angle1a = angle1 + u1 + (angle2 - u1) * bottomSpreadFactor
520 | val angle1b = angle1 - u1 - (angle2 - u1) * bottomSpreadFactor
521 | val angle2a = (angle1 + fPI - u2 - (fPI - u2 - angle2) * topSpreadFactor)
522 | val angle2b = (angle1 - fPI + u2 + (fPI - u2 - angle2) * topSpreadFactor)
523 |
524 | val p1a = getVector(angle1a, radius1).let { (it.first + circle1.centerX()) to (it.second + circle1.centerY()) }.toList()
525 | val p1b = getVector(angle1b, radius1).let { (it.first + circle1.centerX()) to (it.second + circle1.centerY()) }.toList()
526 | val p2a = getVector(angle2a, radius2).let { (it.first + circle2.centerX()) to (it.second + circle2.centerY()) }.toList()
527 | val p2b = getVector(angle2b, radius2).let { (it.first + circle2.centerX()) to (it.second + circle2.centerY()) }.toList()
528 |
529 | val totalRadius = (radius1 + radius2)
530 | val d2Base = min(
531 | max(topSpreadFactor, bottomSpreadFactor) * handleRate,
532 | getVectorLength(p1a[0], p1a[1], p2a[0], p2a[1]) / totalRadius)
533 |
534 | // case circles are overlapping:
535 | val d2 = d2Base * min(1.0f, d * 2 / (radius1 + radius2))
536 |
537 | val r1 = radius1 * d2
538 | val r2 = radius2 * d2
539 |
540 | val pi2 = fPI / 2
541 | val sp1 = getVector(angle1a - pi2, r1).toList()
542 | val sp2 = getVector(angle2a + pi2, r2).toList()
543 | val sp3 = getVector(angle2b - pi2, r2).toList()
544 | val sp4 = getVector(angle1b + pi2, r1).toList()
545 |
546 | // move bottom point to bar top border
547 | val yOffset = (abs(topBorder - p1a[1]) * riseRatio) - 1
548 | val fp1a = p1a.let { l -> listOf(l[0], l[1] - yOffset) }
549 | val fp1b = p1b.let { l -> listOf(l[0], l[1] - yOffset) }
550 |
551 | with(path) {
552 | reset()
553 | moveTo(fp1a[0], fp1a[1] + cornerRadius)
554 | lineTo(fp1a[0], fp1a[1])
555 | cubicTo(fp1a[0] + sp1[0], fp1a[1] + sp1[1], p2a[0] + sp2[0], p2a[1] + sp2[1], p2a[0], p2a[1])
556 | lineTo(circle2.centerX(), circle2.centerY())
557 | lineTo(p2b[0], p2b[1])
558 | cubicTo(p2b[0] + sp3[0], p2b[1] + sp3[1], fp1b[0] + sp4[0], fp1b[1] + sp4[1], fp1b[0], fp1b[1])
559 | lineTo(fp1b[0], fp1b[1] + cornerRadius)
560 | close()
561 | }
562 |
563 | with(canvas) {
564 | drawPath(path, paint)
565 | drawOval(circle2, paint)
566 | }
567 | }
568 |
569 | private fun drawText(canvas: Canvas, paint: Paint,
570 | text: String, align: Paint.Align, color: Int, offset: Float,
571 | holderRect: RectF, textRect: Rect) {
572 | paint.color = color
573 | paint.textAlign = align
574 | paint.getTextBounds(text, 0, text.length, textRect)
575 | val x = when (align) {
576 | Paint.Align.LEFT -> offset
577 | Paint.Align.CENTER -> holderRect.centerX()
578 | Paint.Align.RIGHT -> holderRect.right - offset
579 | }
580 | val y = holderRect.centerY() + textRect.height() / 2f - textRect.bottom
581 | canvas.drawText(text, 0, text.length, x, y, paint)
582 | }
583 |
584 | private fun showLabel(distance: Float) {
585 | val top = barVerticalOffset - distance
586 | val labelVOffset = (topCircleDiameter - labelRectDiameter) / 2f
587 |
588 | val animation = ValueAnimator.ofFloat(rectTopCircle.top, top)
589 | animation.addUpdateListener {
590 | val value = it.animatedValue as Float
591 | rectTopCircle.offsetTo(rectTopCircle.left, value)
592 | rectLabel.offsetTo(rectLabel.left, value + labelVOffset)
593 | invalidate()
594 | }
595 | animation.duration = duration
596 | animation.interpolator = OvershootInterpolator()
597 | animation.start()
598 | }
599 |
600 | private fun hideLabel() {
601 | val labelVOffset = (topCircleDiameter - labelRectDiameter) / 2f
602 | val animation = ValueAnimator.ofFloat(rectTopCircle.top, barVerticalOffset)
603 | animation.addUpdateListener {
604 | val value = it.animatedValue as Float
605 | rectTopCircle.offsetTo(rectTopCircle.left, value)
606 | rectLabel.offsetTo(rectLabel.left, value + labelVOffset)
607 | invalidate()
608 | }
609 | animation.duration = duration
610 | animation.start()
611 | }
612 |
613 | }
--------------------------------------------------------------------------------