├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.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
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ └── com
│ │ │ └── rizlee
│ │ │ └── rangeseekbar_sample
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── rangeseekbar
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── font
│ │ │ └── worksans_semibold.ttf
│ │ ├── drawable-hdpi
│ │ │ ├── thumb_normal.png
│ │ │ ├── thumb_disabled.webp
│ │ │ └── thumb_pressed.png
│ │ ├── drawable-mdpi
│ │ │ ├── thumb_normal.png
│ │ │ ├── thumb_disabled.webp
│ │ │ └── thumb_pressed.png
│ │ ├── drawable-xhdpi
│ │ │ ├── thumb_normal.png
│ │ │ ├── thumb_pressed.png
│ │ │ └── thumb_disabled.webp
│ │ ├── drawable-xxhdpi
│ │ │ ├── thumb_normal.png
│ │ │ ├── thumb_pressed.png
│ │ │ └── thumb_disabled.webp
│ │ ├── drawable-xxxhdpi
│ │ │ ├── thumb_normal.png
│ │ │ ├── thumb_pressed.png
│ │ │ └── thumb_disabled.webp
│ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── attr.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── rizlee
│ │ └── rangeseekbar
│ │ ├── utils
│ │ ├── PixelUtil.kt
│ │ └── BitmapUtil.kt
│ │ └── RangeSeekBar.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── example.png
├── example_2.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── vcs.xml
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── misc.xml
├── runConfigurations.xml
└── gradle.xml
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/rangeseekbar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':rangeseekbar'
2 |
--------------------------------------------------------------------------------
/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/example.png
--------------------------------------------------------------------------------
/example_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/example_2.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RangeSeekBar-sample
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/font/worksans_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/font/worksans_semibold.ttf
--------------------------------------------------------------------------------
/rangeseekbar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-hdpi/thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-hdpi/thumb_normal.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-mdpi/thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-mdpi/thumb_normal.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-hdpi/thumb_disabled.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-hdpi/thumb_disabled.webp
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-hdpi/thumb_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-hdpi/thumb_pressed.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-mdpi/thumb_disabled.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-mdpi/thumb_disabled.webp
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-mdpi/thumb_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-mdpi/thumb_pressed.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xhdpi/thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xhdpi/thumb_normal.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xhdpi/thumb_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xhdpi/thumb_pressed.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_normal.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_pressed.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_normal.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xhdpi/thumb_disabled.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xhdpi/thumb_disabled.webp
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_disabled.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxhdpi/thumb_disabled.webp
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_pressed.png
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_disabled.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rizlee/RangeSeekBar/HEAD/rangeseekbar/src/main/res/drawable-xxxhdpi/thumb_disabled.webp
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 10 14:42:44 MSK 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-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RangeSeekBar
3 |
4 | Left text
5 | Right text
6 | Center text
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rizlee/rangeseekbar_sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rizlee.rangeseekbar_sample
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import kotlinx.android.synthetic.main.activity_main.*
6 |
7 | class MainActivity : AppCompatActivity() {
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | setContentView(R.layout.activity_main)
12 |
13 | rangeSeekBar4.setCurrentValues(2.2f, 5.7f)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/java/com/rizlee/rangeseekbar/utils/PixelUtil.kt:
--------------------------------------------------------------------------------
1 | package com.rizlee.rangeseekbar.utils
2 |
3 | import android.content.Context
4 | import android.util.DisplayMetrics
5 |
6 | object PixelUtil {
7 |
8 | fun dpToPx(context: Context, dp: Int) = Math.round(dp * getPixelScaleFactor(context))
9 |
10 | fun pxToDp(context: Context, px: Int) = Math.round(px / getPixelScaleFactor(context))
11 |
12 | private fun getPixelScaleFactor(context: Context) = context.resources.displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT
13 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/rangeseekbar/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 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/java/com/rizlee/rangeseekbar/utils/BitmapUtil.kt:
--------------------------------------------------------------------------------
1 | package com.rizlee.rangeseekbar.utils
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Canvas
5 | import android.graphics.drawable.BitmapDrawable
6 | import android.graphics.drawable.Drawable
7 |
8 | object BitmapUtil {
9 |
10 | fun toBitmap(drawable: Drawable): Bitmap {
11 | if (drawable is BitmapDrawable) return drawable.bitmap
12 | drawable.apply {
13 | val width = if (!bounds.isEmpty) bounds.width() else intrinsicWidth
14 | val height = if (!bounds.isEmpty) bounds.height() else intrinsicHeight
15 |
16 | val bitmap = Bitmap.createBitmap(if (width <= 0) 1 else width, if (height <= 0) 1 else height,
17 | Bitmap.Config.ARGB_8888)
18 | Canvas(bitmap).apply {
19 | setBounds(0, 0, width, height)
20 | draw(this)
21 | }
22 | return bitmap
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/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.rizlee.rangeseekbar_sample"
11 | minSdkVersion 16
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 | }
24 |
25 | dependencies {
26 | implementation project(':rangeseekbar')
27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
28 | implementation 'androidx.appcompat:appcompat:1.0.2'
29 | implementation 'androidx.core:core-ktx:1.0.2'
30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/res/values/attr.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 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/rangeseekbar/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android-extensions'
3 | apply plugin: 'kotlin-android'
4 |
5 | def versionMajor = 1
6 | def versionMinor = 0
7 | def versionPatch = 0
8 | def versionBuild = 0
9 |
10 | ext {
11 | bintrayRepo = 'rangeseekbar'
12 | bintrayName = 'rangeseekbar'
13 |
14 | publishedGroupId = 'com.rizlee.view'
15 | libraryName = 'RangeSeekBar'
16 | artifact = 'rangeseekbar'
17 |
18 | libraryDescription = 'RangeSeekBar for android'
19 |
20 | siteUrl = 'https://github.com/Rizlee/RangeSeekBar'
21 | gitUrl = 'https://github.com/Rizlee/RangeSeekBar.git'
22 |
23 | libraryVersion = "${versionMajor}.${versionMinor}.${versionPatch}"
24 |
25 | developerId = 'rizlee'
26 | developerName = 'Evgen Tretsyak'
27 | developerEmail = 'evgentretsyak@gmail.com'
28 |
29 | licenseName = 'The Apache Software License, Version 2.0'
30 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
31 | allLicenses = ["Apache-2.0"]
32 | }
33 |
34 | android {
35 | compileSdkVersion 28
36 |
37 | defaultConfig {
38 | minSdkVersion 21
39 | targetSdkVersion 28
40 | versionCode 1
41 | versionName "1.0"
42 |
43 | versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
44 | versionName "${versionMajor}.${versionMinor}.${versionPatch}"
45 |
46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
47 | vectorDrawables.useSupportLibrary = true
48 | }
49 |
50 | buildTypes {
51 | release {
52 | minifyEnabled false
53 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
54 | }
55 | }
56 |
57 | sourceSets {
58 | main.java.srcDirs += 'src/main/kotlin'
59 | }
60 |
61 | lintOptions {
62 | abortOnError false
63 | }
64 |
65 | }
66 |
67 | dependencies {
68 | implementation(
69 | "com.android.support:appcompat-v7:28.0.0",
70 | "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
71 | )
72 | }
73 | repositories {
74 | mavenCentral()
75 | }
76 |
77 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
78 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
79 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
25 |
26 |
40 |
41 |
62 |
63 |
83 |
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.android.com)
2 | [](https://android-arsenal.com/api?level=21)
3 | [  ](https://bintray.com/rizlee/rangeseekbar/rangeseekbar/1.0.0/link)
4 |
5 | # Usage
6 | #### Gradle
7 | ```xml
8 | implementation "com.rizlee.view:rangeseekbar:$latest_version"
9 | ```
10 |
11 | #### XML
12 | ```xml
13 |
29 | ```
30 |
31 | #### Kotlin
32 | ```kotlin
33 | just implement:
34 | OnRangeSeekBarPostListener
35 | or
36 | OnRangeSeekBarRealTimeListener
37 |
38 | rangeSeekBar.listenerPost = this
39 | or
40 | rangeSeekBarlistenerRealTime = this
41 | ```
42 |
43 | 
44 |
45 | 
46 |
47 | **Attributes**
48 |
49 | Attribute | Type | Description | Default value
50 | -----------|-------|-------------|-----------
51 | minValue | float | min value of range | 0f
52 | maxValue | float | max value of range | 100f
53 | stepValue | float | it must be: (maxValue - minValue) % stepValue == 0f | 1f
54 | valueType | intType, floatType | to display values in the form you want | intType
55 | enableGradient | boolean | enable or disable gradient between center and sides | false
56 | textColor | color | change all text color on view | Color.GRAY
57 | showThumbsText | none, below, above, center | numeric values of selected range | none
58 | thumbsTextMargin | dimension | margin from center of seekbar(сan be negative) | 0dp
59 | additionalTextMargin | dimension | margin from center of seekBar(сan be negative) | 0dp
60 | showAdditionalText | none, below, above, center | additional info on seekbar(left, center and right text) | none
61 | roundedCorners | boolean | seekbar with rounded corners or not | false
62 | barHeight | dimension | height of bar(**possible problems with large values**) | 8dp
63 | active | boolean | enable or disable seekbar, if seekbar disabled thumb images will be replaced by others (thumbsDisabled) | true
64 | textSize | dimension | change all text size on view | 12sp
65 | textFont | not ready yeat | - | worksans-semibold
66 | centerText | string | - | Center text
67 | rightText | string | - | Right text
68 | leftText | string | - | Left Text
69 | thumbsNormal | drawable | - | thumb_normal.png
70 | thumbsPressed | drawable | - | thumb_pressed.png
71 | thumbsDisabled | drawable | - | thumb_disabled.png
72 | sideColor | color | colors left and right of thumbs | Color.RED
73 | transitionColor | color | color used in gradient formation, if *enableGradient* = false, this color not used | Color.YELLOW
74 | centerColor | color | color between left and right thumbs | Color.GREEN
75 |
76 | **Functions**
77 |
78 | Function name | Description |
79 | ---------------- | ------------------------------ |
80 | `setRange(minValue: Float, maxValue: Float, stepValue: Float)` | set range values with valueType = float |
81 | `setRange(minValue: Int, maxValue: Int, stepValue: Int)` | set range values with valueType = int |
82 | `setCurrentValues(leftValue: Float, rightValue: Float)` | set selected values
83 | `setCurrentValues(leftValue: Int, rightValue: Int)` | set selected values
84 | `getRangeInfo()` | return RangeInfo(val minValue: Float, val maxValue: Float, val stepValue: Float)
85 | `getCurrentValues()` | return Range(val leftValue: Float, val rightValue: Float)
86 |
87 | **Public fields(getters and setters)**
88 |
89 | Field name: type | Attribute equivalent |
90 | ------------- | ------------- |
91 | isActive: Boolean | active
92 | leftText: String | leftText
93 | rightText: String | rightText
94 | centerText: String | centerText
95 | sideBarColor: Int | sideColor
96 | centerBarColor: Int | centerColor
97 | transitionBarColor: Int | transitionColor
98 | isGradientNeed: Int | enableGradient
99 |
100 | **Interfaces**
101 |
102 | Field name | Type | Description |
103 | -------------| ------| -------------|
104 | listenerPost | OnRangeSeekBarPostListener | sends results after a user has finished changing them.
105 | listenerRealTime | OnRangeSeekBarRealTimeListener | sends results while changing values
106 |
107 |
108 | *If you need some new features or you found bugs please write issue
109 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/rangeseekbar/src/main/java/com/rizlee/rangeseekbar/RangeSeekBar.kt:
--------------------------------------------------------------------------------
1 | package com.rizlee.rangeseekbar
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.os.Bundle
7 | import android.os.Parcelable
8 | import android.util.AttributeSet
9 | import android.view.MotionEvent
10 | import android.view.View
11 | import android.view.ViewConfiguration
12 | import androidx.annotation.ColorInt
13 | import androidx.core.content.res.ResourcesCompat
14 | import com.rizlee.rangeseekbar.utils.BitmapUtil
15 | import com.rizlee.rangeseekbar.utils.PixelUtil
16 | import kotlin.math.abs
17 | import kotlin.math.max
18 | import kotlin.math.min
19 |
20 | const val INT = 6
21 | const val FLOAT = 7
22 |
23 | private const val DEFAULT_MIN = 0f
24 | private const val DEFAULT_MAX = 100f
25 | private const val DEFAULT_STEP = 1f
26 |
27 | private const val DEFAULT_BAR_HEIGHT_IN_DP = 8
28 | private const val DEFAULT_TEXT_SIZE_IN_DP = 12
29 | private const val DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP = 8
30 | private const val TEXT_LATERAL_PADDING_IN_DP = 3
31 |
32 | private const val THUMB_TEXT_POSITION_NONE = 0
33 | private const val THUMB_TEXT_POSITION_BELOW = 1
34 | private const val THUMB_TEXT_POSITION_ABOVE = 2
35 | private const val THUMB_TEXT_POSITION_CENTER = 3
36 |
37 | private const val THUMB_TEXT_MARGIN_IN_DP = 0
38 | private const val ADDITIONAL_TEXT_MARGIN_IN_DP = 0
39 |
40 | private const val ADDITIONAL_TEXT_POSITION_NONE = 0
41 | private const val ADDITIONAL_TEXT_POSITION_BELOW = 1
42 | private const val ADDITIONAL_TEXT_POSITION_ABOVE = 2
43 | private const val ADDITIONAL_TEXT_POSITION_CENTER = 3
44 |
45 | private const val INVALID_POINTER_ID = 255
46 |
47 | class RangeSeekBar @JvmOverloads constructor(
48 | context: Context,
49 | private val attrs: AttributeSet? = null,
50 | defStyleAttr: Int = 0
51 | ) : View(context, attrs, defStyleAttr) {
52 |
53 | /* Attrs values */
54 | private lateinit var thumbImage: Bitmap
55 | private lateinit var thumbDisabledImage: Bitmap // when isActive = false
56 | private lateinit var thumbPressedImage: Bitmap
57 |
58 | private var minValue = DEFAULT_MIN
59 | private var maxValue = DEFAULT_MAX
60 | private var stepValue = DEFAULT_STEP
61 | private var valueType = INT
62 |
63 | var isActive = true //dragable or not
64 | set(value) {
65 | field = value; invalidate()
66 | }
67 |
68 | var leftText: String = getContext().getString(R.string.text_left)
69 | set(value){
70 | field = value
71 | invalidate()
72 | }
73 | var rightText: String = getContext().getString(R.string.text_right)
74 | set(value){
75 | field = value
76 | invalidate()
77 | }
78 | var centerText: String = getContext().getString(R.string.text_center)
79 | set(value){
80 | field = value
81 | invalidate()
82 | }
83 |
84 | @ColorInt
85 | var sideBarColor = Color.RED
86 | set(value){
87 | field = value
88 | invalidate()
89 | }
90 | @ColorInt
91 | var centerBarColor = Color.GREEN
92 | set(value){
93 | field = value
94 | invalidate()
95 | }
96 | @ColorInt
97 | var transitionBarColor = Color.YELLOW
98 | set(value){
99 | field = value
100 | invalidate()
101 | }
102 | var isGradientNeed = false
103 | set(value){
104 | field = value
105 | invalidate()
106 | }
107 |
108 | var listenerPost: OnRangeSeekBarPostListener? = null
109 | var listenerRealTime: OnRangeSeekBarRealTimeListener? = null
110 |
111 | private var textColor = Color.GRAY
112 | private var textFont = ResourcesCompat.getFont(getContext(), R.font.worksans_semibold)
113 | private var textSize = PixelUtil.dpToPx(getContext(), DEFAULT_TEXT_SIZE_IN_DP)
114 | private var thumbTextPosition = THUMB_TEXT_POSITION_NONE
115 | private var additionalTextPosition = ADDITIONAL_TEXT_POSITION_NONE
116 |
117 | private var isRoundedCorners = false
118 |
119 | private var barHeight = PixelUtil.dpToPx(getContext(), DEFAULT_BAR_HEIGHT_IN_DP)
120 |
121 | private var additionalTextMargin = ADDITIONAL_TEXT_MARGIN_IN_DP
122 | private var thumbTextMargin = THUMB_TEXT_MARGIN_IN_DP
123 |
124 | /* System values */
125 | private var isDragging = false
126 |
127 | private var stepsCount = 0
128 |
129 | private val scaledTouchSlop by lazy { ViewConfiguration.get(getContext()).scaledTouchSlop }
130 |
131 | private var activePointerId = INVALID_POINTER_ID
132 | private var downMotionX = 0f
133 |
134 | private var padding = 0f
135 | private var distanceToTop = 0
136 | //private var thumbTextOffset = 0
137 |
138 | private var normalizedMinValue = 0.0
139 | private var normalizedMaxValue = 1.0
140 |
141 | private var pressedThumb: Thumb? = null
142 |
143 | private lateinit var rectF: RectF
144 | private lateinit var center: Point
145 |
146 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
147 |
148 | init {
149 | init()
150 | }
151 |
152 | private fun init() {
153 | context.resources.apply {
154 | BitmapUtil.apply {
155 | thumbImage = toBitmap(getDrawable(R.drawable.thumb_normal))
156 | thumbPressedImage = toBitmap(getDrawable(R.drawable.thumb_pressed))
157 | thumbDisabledImage = toBitmap(getDrawable(R.drawable.thumb_disabled))
158 | }
159 | }
160 |
161 | attrs?.let {
162 | context.obtainStyledAttributes(it, R.styleable.RangeSeekBar).apply {
163 | getDrawable(R.styleable.RangeSeekBar_thumbsNormal)?.let { drawable -> thumbImage = BitmapUtil.toBitmap(drawable) }
164 | getDrawable(R.styleable.RangeSeekBar_thumbsDisabled)?.let { drawable -> thumbDisabledImage = BitmapUtil.toBitmap(drawable) }
165 | getDrawable(R.styleable.RangeSeekBar_thumbsPressed)?.let { drawable -> thumbPressedImage = BitmapUtil.toBitmap(drawable) }
166 |
167 | centerBarColor = getColor(R.styleable.RangeSeekBar_centerColor, centerBarColor)
168 | sideBarColor = getColor(R.styleable.RangeSeekBar_sideColor, sideBarColor)
169 | transitionBarColor = getColor(R.styleable.RangeSeekBar_transitionColor, transitionBarColor)
170 | isGradientNeed = getBoolean(R.styleable.RangeSeekBar_enableGradient, isGradientNeed)
171 |
172 | textColor = getColor(R.styleable.RangeSeekBar_textColor, textColor)
173 | //textFont = this.resources.getFont(R.styleable.RangeSeekBar_textFont) // todo font
174 | thumbTextPosition = getInt(R.styleable.RangeSeekBar_showThumbsText, thumbTextPosition)
175 | additionalTextPosition = getInt(R.styleable.RangeSeekBar_showAdditionalText, additionalTextPosition)
176 | textSize = getDimensionPixelSize(R.styleable.RangeSeekBar_textSize, textSize) //todo maybe problems with text size here
177 |
178 | centerText = getString(R.styleable.RangeSeekBar_centerText) ?: centerText
179 | leftText = getString(R.styleable.RangeSeekBar_leftText) ?: leftText
180 | rightText = getString(R.styleable.RangeSeekBar_rightText) ?: rightText
181 |
182 | isActive = getBoolean(R.styleable.RangeSeekBar_active, isActive)
183 |
184 | minValue = getFloat(R.styleable.RangeSeekBar_minValue, minValue)
185 | maxValue = getFloat(R.styleable.RangeSeekBar_maxValue, maxValue)
186 | stepValue = getFloat(R.styleable.RangeSeekBar_stepValue, stepValue)
187 | if (maxValue < minValue) throw Exception("Min value can't be higher than max value")
188 | if (!stepValueValidation(minValue, maxValue, stepValue)) throw Exception("Incorrect min/max/step, it must be: (maxValue - minValue) % stepValue == 0f")
189 | valueType = getInt(R.styleable.RangeSeekBar_valueType, valueType)
190 | stepsCount = ((maxValue - minValue) / stepValue).toInt()
191 |
192 | isRoundedCorners = getBoolean(R.styleable.RangeSeekBar_roundedCorners, isRoundedCorners)
193 |
194 | barHeight = getDimensionPixelSize(R.styleable.RangeSeekBar_barHeight, barHeight)
195 |
196 | additionalTextMargin = getDimensionPixelSize(R.styleable.RangeSeekBar_additionalTextMargin, additionalTextMargin)
197 | thumbTextMargin = getDimensionPixelSize(R.styleable.RangeSeekBar_thumbsTextMargin, thumbTextMargin)
198 |
199 | recycle()
200 | }
201 | }
202 |
203 | distanceToTop = PixelUtil.dpToPx(context, DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP)
204 |
205 | isFocusable = true
206 | isFocusableInTouchMode = true
207 | }
208 |
209 | @SuppressLint("ClickableViewAccessibility")
210 | override fun onTouchEvent(event: MotionEvent?): Boolean {
211 | if (isActive) {
212 | if (!isEnabled) return false
213 | event?.let { event ->
214 | var pointerIndex: Int
215 | when (event.action and MotionEvent.ACTION_MASK) {
216 | MotionEvent.ACTION_DOWN -> {
217 | activePointerId = event.getPointerId(event.pointerCount - 1)
218 | pointerIndex = event.findPointerIndex(activePointerId)
219 | downMotionX = event.getX(pointerIndex)
220 |
221 | pressedThumb = evalPressedThumb(downMotionX)
222 | pressedThumb ?: return super.onTouchEvent(event)
223 |
224 | isPressed = true
225 | invalidate()
226 | isDragging = true
227 | trackTouchEvent(event)
228 | parent?.requestDisallowInterceptTouchEvent(true)
229 | println()
230 | }
231 |
232 | MotionEvent.ACTION_MOVE -> {
233 | pressedThumb?.let {
234 | listenerRealTime?.let {
235 | when (valueType) {
236 | INT -> it.onValuesChanging(getSelectedMinValue().toInt(), getSelectedMaxValue().toInt())
237 | FLOAT -> it.onValuesChanging(getSelectedMinValue().toFloat(), getSelectedMaxValue().toFloat())
238 | else -> throw Exception("Unknown value type")
239 | }
240 | }
241 |
242 | if (isDragging) {
243 | trackTouchEvent(event)
244 | } else {
245 | pointerIndex = event.findPointerIndex(activePointerId)
246 | val x = event.getX(pointerIndex)
247 |
248 | if (abs(x - downMotionX) > scaledTouchSlop) {
249 | isPressed = true
250 | invalidate()
251 | isDragging = true
252 | trackTouchEvent(event)
253 | parent?.requestDisallowInterceptTouchEvent(true)
254 | println()
255 | }
256 | }
257 | }
258 | println()
259 | }
260 |
261 | MotionEvent.ACTION_UP -> {
262 | if (isDragging) {
263 | trackTouchEvent(event)
264 | isDragging = false
265 | isPressed = false
266 | } else {
267 | isDragging = true
268 | trackTouchEvent(event)
269 | isDragging = false
270 | }
271 |
272 | listenerPost?.let {
273 | when (valueType) {
274 | INT -> it.onValuesChanged(getSelectedMinValue().toInt(), getSelectedMaxValue().toInt())
275 | FLOAT -> it.onValuesChanged(getSelectedMinValue().toFloat(), getSelectedMaxValue().toFloat())
276 | else -> throw Exception("Unknown value type")
277 | }
278 | }
279 |
280 | pressedThumb = null
281 | invalidate()
282 | }
283 | MotionEvent.ACTION_POINTER_DOWN -> {
284 | val index = event.pointerCount - 1
285 | downMotionX = event.getX(index)
286 | activePointerId = event.getPointerId(index)
287 | invalidate()
288 | }
289 |
290 | MotionEvent.ACTION_POINTER_UP -> {
291 | onSecondaryPointerUp(event)
292 | invalidate()
293 | }
294 | MotionEvent.ACTION_CANCEL -> {
295 | if (isDragging) isDragging = false; isPressed = false
296 | invalidate()
297 | }
298 | }
299 | } ?: run { return false }
300 | }
301 | return true
302 | }
303 |
304 | @SuppressLint("DrawAllocation")
305 | override fun onDraw(canvas: Canvas?) {
306 | super.onDraw(canvas)
307 |
308 | center = Point(width / 2, height / 2)
309 | rectF = RectF(padding,
310 | center.y - barHeight / 2.toFloat(),
311 | width - padding,
312 | center.y + barHeight / 2.toFloat())
313 |
314 | paint.textSize = textSize.toFloat()
315 | paint.style = Paint.Style.FILL
316 | paint.typeface = textFont
317 | paint.isAntiAlias = true
318 |
319 | padding = getThumbHalfWidth()
320 |
321 | /* pre draw rect */
322 | rectF.left = padding
323 | rectF.right = width - padding
324 |
325 | canvas?.apply {
326 | //drawRect(rectF, paint) // this line just for debugging
327 |
328 | /* center rect */
329 | rectF.left = normalizedToScreen(normalizedMinValue)
330 | rectF.right = normalizedToScreen(normalizedMaxValue)
331 |
332 | /* left rect */
333 | val rectUnusedArea = RectF()
334 | rectUnusedArea.top = rectF.top
335 | rectUnusedArea.bottom = rectF.bottom
336 | rectUnusedArea.left = padding
337 | rectUnusedArea.right = rectF.left
338 |
339 | if (isGradientNeed) {
340 | paint.shader = LinearGradient(
341 | rectUnusedArea.left,
342 | 0f,
343 | rectUnusedArea.right,
344 | 0f,
345 | intArrayOf(sideBarColor, sideBarColor, transitionBarColor),
346 | floatArrayOf(0f, 0.5f, 1f),
347 | Shader.TileMode.REPEAT)
348 | } else {
349 | paint.color = sideBarColor
350 | }
351 | if (!isRoundedCorners) {
352 | drawRect(rectUnusedArea, paint)
353 | } else {
354 | val bufRect = RectF(rectUnusedArea)
355 | bufRect.left += 10
356 | drawRect(bufRect, paint)
357 |
358 | drawRoundRect(rectUnusedArea, 10f, 10f, paint)
359 | }
360 |
361 | /* right rect */
362 | rectUnusedArea.right = width - padding
363 | rectUnusedArea.left = rectF.right
364 |
365 | if (isGradientNeed) {
366 | paint.shader = LinearGradient(
367 | rectUnusedArea.right,
368 | 0f,
369 | rectUnusedArea.left,
370 | 0f,
371 | intArrayOf(sideBarColor, sideBarColor, transitionBarColor),
372 | floatArrayOf(0f, 0.5f, 1f),
373 | Shader.TileMode.REPEAT)
374 | } else {
375 | paint.color = sideBarColor
376 | }
377 | if (!isRoundedCorners) {
378 | drawRect(rectUnusedArea, paint)
379 | } else {
380 | val bufRect1 = RectF(rectUnusedArea)
381 | bufRect1.right -= 10
382 | drawRect(bufRect1, paint)
383 |
384 | drawRoundRect(rectUnusedArea, 10f, 10f, paint)
385 | }
386 |
387 | rectF.left = normalizedToScreen(normalizedMinValue)
388 | rectF.right = normalizedToScreen(normalizedMaxValue)
389 |
390 | if (isGradientNeed) {
391 | paint.shader = LinearGradient(
392 | rectF.left,
393 | 0f,
394 | rectF.right,
395 | 0f,
396 | intArrayOf(transitionBarColor, centerBarColor, transitionBarColor),
397 | floatArrayOf(0f, 0.5f, 1f),
398 | Shader.TileMode.REPEAT)
399 | } else {
400 | paint.color = centerBarColor
401 | }
402 | drawRect(rectF, paint)
403 |
404 | // todo maybe need check on isActive
405 |
406 | /* clear gradient */
407 | paint.shader = null
408 |
409 | /* draw thumb */
410 | drawThumb(normalizedToScreen(normalizedMaxValue), Thumb.MAX == pressedThumb, canvas)
411 | drawThumb(normalizedToScreen(normalizedMinValue), Thumb.MIN == pressedThumb, canvas)
412 |
413 | /* draw text (thumb values) if need and left/center/right text if need*/
414 | paint.textSize = textSize.toFloat()
415 | paint.color = textColor //todo in future change textColor thumb values and left/center/right text
416 |
417 | val minText = removeRedundantNumberPart(getSelectedMinValue().toString())
418 | val maxText = removeRedundantNumberPart(getSelectedMaxValue().toString())
419 |
420 | val minTextWidth = paint.measureText(minText)
421 | val maxTextWidth = paint.measureText(maxText)
422 | val centerTextWidth = paint.measureText(centerText)
423 |
424 | var minPosition = max(0f, normalizedToScreen(normalizedMinValue) - minTextWidth * 0.5f)
425 | var maxPosition = min(width - maxTextWidth, normalizedToScreen(normalizedMaxValue) - maxTextWidth * 0.5f)
426 |
427 | val spacing = PixelUtil.dpToPx(context, TEXT_LATERAL_PADDING_IN_DP)
428 | val overlap = minPosition + minTextWidth - maxPosition + spacing
429 | if (overlap > 0f) {
430 | minPosition -= (overlap * normalizedMinValue / (normalizedMinValue + 1 - normalizedMaxValue)).toFloat()
431 | maxPosition += (overlap * (1 - normalizedMaxValue) / (normalizedMinValue + 1 - normalizedMaxValue)).toFloat()
432 | }
433 |
434 | if (thumbTextPosition != THUMB_TEXT_POSITION_NONE) {
435 | val yPosition = when (thumbTextPosition) {
436 | ADDITIONAL_TEXT_POSITION_BELOW -> {
437 | center.y + barHeight / 2 + getThumbHalfHeight() + thumbTextMargin
438 | }
439 | ADDITIONAL_TEXT_POSITION_ABOVE -> {
440 | center.y + barHeight / 2 - getThumbHalfHeight() - thumbTextMargin
441 | }
442 | ADDITIONAL_TEXT_POSITION_CENTER -> {
443 | (center.y + barHeight / 2).toFloat()
444 | }
445 | else -> {
446 | (center.y + barHeight / 2).toFloat()
447 | }
448 | }
449 | drawText(minText,
450 | minPosition,
451 | yPosition,
452 | paint)
453 |
454 | drawText(maxText,
455 | maxPosition,
456 | yPosition,
457 | paint)
458 | }
459 |
460 | val leftTextWidth = padding + paint.measureText(leftText) + spacing.toFloat()
461 | val rightTextWidth = padding + paint.measureText(rightText) + spacing.toFloat()
462 | val centerTextPosition = (maxPosition + minPosition) * 0.5f - centerTextWidth * 0.5f + spacing * 4
463 | var textPosition = centerTextPosition
464 |
465 | if (centerTextPosition < leftTextWidth + spacing) textPosition = leftTextWidth + spacing
466 | if (centerTextPosition + centerTextWidth > width - rightTextWidth - padding + spacing * 4) textPosition = width - rightTextWidth - padding - centerTextWidth + spacing * 4
467 |
468 | if (additionalTextPosition != ADDITIONAL_TEXT_POSITION_NONE) {
469 | val yPosition = when (additionalTextPosition) {
470 | ADDITIONAL_TEXT_POSITION_BELOW -> {
471 | center.y + barHeight / 2 + getThumbHalfHeight() + additionalTextMargin
472 | }
473 | ADDITIONAL_TEXT_POSITION_ABOVE -> {
474 | center.y + barHeight / 2 - getThumbHalfHeight() - additionalTextMargin
475 | }
476 | ADDITIONAL_TEXT_POSITION_CENTER -> {
477 | (center.y + barHeight / 2).toFloat()
478 | }
479 | else -> {
480 | (center.y + barHeight / 2).toFloat()
481 | }
482 | }
483 |
484 | drawText(centerText,
485 | textPosition,
486 | yPosition,
487 | paint)
488 |
489 | drawText(leftText,
490 | normalizedToScreen(0.0),
491 | yPosition,
492 | paint)
493 |
494 | drawText(rightText,
495 | normalizedToScreen(1.0) - rightTextWidth + spacing + padding,
496 | yPosition,
497 | paint)
498 | }
499 | }
500 | }
501 |
502 | private fun stepValueValidation(minValue: Float, maxValue: Float, stepValue: Float) = (maxValue.toBigDecimal() - minValue.toBigDecimal()) % stepValue.toBigDecimal() == 0f.toBigDecimal()
503 |
504 | private fun removeRedundantNumberPart(number: String) =
505 | when (valueType) {
506 | INT -> number.substring(0, number.indexOf("."))
507 | FLOAT -> stepValue.toString().apply {
508 | return when {
509 | contains(".") -> {
510 | val numbersCountDifference = (length - indexOf(".") - 1) - (number.length - number.indexOf(".") - 1)
511 | when {
512 | (numbersCountDifference) > 0 -> {
513 | var stringWithZeros = ""
514 | for (i in 1..abs(numbersCountDifference))
515 | stringWithZeros += "0"
516 | number + stringWithZeros
517 | }
518 | (numbersCountDifference) < 0 -> number.substring(0, number.indexOf(".") + (length - indexOf(".")))
519 | else -> number
520 | }
521 | }
522 | number.contains(".") -> number.substring(0, number.indexOf(".") + 2)
523 | else -> number
524 | }
525 | }
526 | else -> throw Exception("Invalid type or values")
527 | }
528 |
529 | private fun drawThumb(screenCoord: Float, pressed: Boolean, canvas: Canvas) {
530 | val buttonToDraw = if (isActive) {
531 | if (pressed) thumbPressedImage else thumbImage
532 | } else {
533 | thumbDisabledImage
534 | }
535 |
536 | canvas.drawBitmap(buttonToDraw,
537 | screenCoord - if (pressed) getThumbPressedHalfWidth() else getThumbHalfWidth(),
538 | center.y.toFloat() - getThumbHalfHeight(),
539 | paint)
540 | }
541 |
542 | private fun evalPressedThumb(touchX: Float): Thumb? {
543 | val minThumbPressed = isInThumbRange(touchX, normalizedMinValue)
544 | val maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue)
545 | return if (minThumbPressed && maxThumbPressed) {
546 | if (touchX / width > 0.5f) Thumb.MIN else Thumb.MAX
547 | } else if (minThumbPressed) {
548 | Thumb.MIN
549 | } else if (maxThumbPressed) {
550 | Thumb.MAX
551 | } else null
552 | }
553 |
554 | private fun onSecondaryPointerUp(ev: MotionEvent) {
555 | (ev.action and MotionEvent.ACTION_POINTER_ID_MASK shr MotionEvent.ACTION_POINTER_ID_SHIFT).apply {
556 | if (ev.getPointerId(this) == activePointerId) {
557 | val newPointerIndex = if (this == 0) 1 else 0
558 | downMotionX = ev.getX(newPointerIndex)
559 | activePointerId = ev.getPointerId(newPointerIndex)
560 | }
561 | }
562 | }
563 |
564 | private fun isInThumbRange(touchX: Float, normalizedThumbValue: Double) = abs(touchX - normalizedToScreen(normalizedThumbValue)) <= getThumbHalfWidth()
565 |
566 | private fun normalizedToScreen(normalizedPos: Double) = (padding + normalizedPos * (width - 2 * padding)).toFloat()
567 |
568 | private fun getThumbHalfHeight() = thumbImage.height * 0.5f
569 | private fun getThumbHalfWidth() = thumbImage.width * 0.5f
570 | private fun getThumbPressedHalfHeight() = thumbPressedImage.height * 0.5f
571 | private fun getThumbPressedHalfWidth() = thumbPressedImage.width * 0.5f
572 |
573 | private fun trackTouchEvent(event: MotionEvent) {
574 | val pointerIndex = event.findPointerIndex(activePointerId)
575 | val x = event.getX(pointerIndex)
576 |
577 | if (Thumb.MIN == pressedThumb) {
578 | setNormalizedMinValue(screenToNormalized(x))
579 | } else if (Thumb.MAX == pressedThumb) {
580 | setNormalizedMaxValue(screenToNormalized(x))
581 | }
582 | }
583 |
584 | private fun setNormalizedMinValue(value: Double) {
585 | normalizedMinValue = max(0.0, min(1.0, min(value, normalizedMaxValue)))
586 | invalidate()
587 | }
588 |
589 | private fun setNormalizedMaxValue(value: Double) {
590 | normalizedMaxValue = max(0.0, min(1.0, max(value, normalizedMinValue)))
591 | invalidate()
592 | }
593 |
594 | private fun screenToNormalized(screenPos: Float): Double {
595 | val width = width
596 | return if (width <= 2 * padding) {
597 | 0.0 //divide by zero safe
598 | } else {
599 | val result = ((screenPos - padding) / (width - 2 * padding)).toDouble()
600 | min(1.0, max(0.0, result))
601 | }
602 | }
603 |
604 | private fun valueToNormalize(value: Double) = ((maxValue - minValue) * (value * 100)) / 100 + minValue
605 | private fun normalizeToValue(value: Float) = (((value - minValue) * 100) / (maxValue - minValue)) / 100
606 |
607 | private fun getSelectedMinValue() = getValueAccordingToStep(valueToNormalize(normalizedMinValue))
608 | private fun getSelectedMaxValue() = getValueAccordingToStep(valueToNormalize(normalizedMaxValue))
609 |
610 | private fun getValueAccordingToStep(value: Double) = ((value.toBigDecimal() / stepValue.toBigDecimal()).toInt()).toBigDecimal() * stepValue.toBigDecimal()
611 |
612 | private fun resetRange() {
613 | if (maxValue < minValue) throw Exception("Min value can't be higher than max value")
614 | if (!stepValueValidation(minValue, maxValue, stepValue)) throw Exception("Incorrect min/max/step, it must be: (maxValue - minValue) % stepValue == 0f")
615 |
616 | normalizedMinValue = 0.0
617 | normalizedMaxValue = 1.0
618 |
619 | invalidate()
620 | }
621 |
622 | fun setRange(minValue: Float, maxValue: Float, stepValue: Float) {
623 | valueType = FLOAT
624 |
625 | this.minValue = minValue
626 | this.maxValue = maxValue
627 | this.stepValue = stepValue
628 |
629 | resetRange()
630 | }
631 |
632 | fun setRange(minValue: Int, maxValue: Int, stepValue: Int) {
633 | valueType = INT
634 |
635 | this.minValue = minValue.toFloat()
636 | this.maxValue = maxValue.toFloat()
637 | this.stepValue = stepValue.toFloat()
638 |
639 | resetRange()
640 | }
641 |
642 | fun setCurrentValues(leftValue: Float, rightValue: Float) {
643 | if (leftValue > rightValue) throw Exception("LeftValue can't be higher than rightValue")
644 | if (leftValue < minValue || rightValue > maxValue) throw Exception("Out of range")
645 | if (!stepValueValidation(leftValue, maxValue, stepValue) || !stepValueValidation(minValue, rightValue, stepValue)) throw Exception("You can't set these values according to your step")
646 | normalizedMinValue = normalizeToValue(leftValue).toDouble()
647 | normalizedMaxValue = normalizeToValue(rightValue).toDouble()
648 |
649 | invalidate()
650 | }
651 |
652 | fun setCurrentValues(leftValue: Int, rightValue: Int) {
653 | if (leftValue > rightValue) throw Exception("LeftValue can't be higher than rightValue")
654 | if (leftValue < minValue || rightValue > maxValue) throw Exception("Out of range")
655 | if (!stepValueValidation(leftValue.toFloat(), maxValue, stepValue) || !stepValueValidation(minValue, rightValue.toFloat(), stepValue)) throw Exception("You can't set these values according to your step")
656 | normalizedMinValue = normalizeToValue(leftValue.toFloat()).toDouble()
657 | normalizedMaxValue = normalizeToValue(rightValue.toFloat()).toDouble()
658 |
659 | invalidate()
660 | }
661 |
662 | fun getRangeInfo() = RangeInfo(minValue, maxValue, stepValue)
663 |
664 | fun getCurrentValues() = Range(getSelectedMinValue().toFloat(), getSelectedMaxValue().toFloat())
665 |
666 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
667 | var width = 200
668 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
669 | width = MeasureSpec.getSize(widthMeasureSpec)
670 | }
671 |
672 | val heightThumbText = if (thumbTextPosition == THUMB_TEXT_POSITION_NONE ||
673 | thumbTextPosition == THUMB_TEXT_POSITION_CENTER) 0 else PixelUtil.dpToPx(context, thumbTextMargin) + PixelUtil.dpToPx(context, textSize) / 2
674 |
675 | val heightAdditionalText = if (additionalTextPosition == ADDITIONAL_TEXT_POSITION_NONE ||
676 | additionalTextPosition == ADDITIONAL_TEXT_POSITION_CENTER) 0 else PixelUtil.dpToPx(context, additionalTextMargin) + PixelUtil.dpToPx(context, textSize) / 2
677 |
678 | var height = thumbImage.height + max(heightThumbText, heightAdditionalText)
679 |
680 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
681 | height = min(height, MeasureSpec.getSize(heightMeasureSpec))
682 | }
683 | setMeasuredDimension(width, height)
684 | }
685 |
686 | override fun onSaveInstanceState(): Parcelable? {
687 | val bundle = Bundle()
688 | bundle.putParcelable("SUPER", super.onSaveInstanceState())
689 | bundle.putDouble("LEFT_VALUE", normalizedMinValue)
690 | bundle.putDouble("RIGHT_VALUE", normalizedMaxValue)
691 |
692 | bundle.putFloat("MIN_VALUE", minValue)
693 | bundle.putFloat("MAX_VALUE", maxValue)
694 |
695 | bundle.putFloat("STEP_VALUE", stepValue)
696 | bundle.putInt("VALUE_TYPE", valueType)
697 |
698 | bundle.putBoolean("IS_ACTIVE", isActive)
699 |
700 | bundle.putString("LEFT_TEXT", leftText)
701 | bundle.putString("RIGHT_TEXT", rightText)
702 | bundle.putString("CENTER_TEXT", centerText)
703 |
704 | bundle.putInt("SIDE_BAR_COLOR", sideBarColor)
705 | bundle.putInt("CENTER_BAR_COLOR", centerBarColor)
706 | bundle.putInt("TRANSITION_BAR_COLOR", transitionBarColor)
707 | bundle.putBoolean("IS_GRADIENT_NEED", isGradientNeed)
708 | return bundle
709 | }
710 |
711 | override fun onRestoreInstanceState(parcel: Parcelable) {
712 | val bundle = parcel as Bundle
713 | super.onRestoreInstanceState(bundle.getParcelable("SUPER"))
714 | normalizedMinValue = bundle.getDouble("LEFT_VALUE")
715 | normalizedMaxValue = bundle.getDouble("RIGHT_VALUE")
716 |
717 | minValue = bundle.getFloat("MIN_VALUE")
718 | maxValue = bundle.getFloat("MAX_VALUE")
719 |
720 | stepValue = bundle.getFloat("STEP_VALUE")
721 | valueType = bundle.getInt("VALUE_TYPE")
722 |
723 | isActive = bundle.getBoolean("IS_ACTIVE")
724 |
725 | leftText = bundle.getString("LEFT_TEXT")
726 | rightText = bundle.getString("RIGHT_TEXT")
727 | centerText = bundle.getString("CENTER_TEXT")
728 |
729 | sideBarColor = bundle.getInt("SIDE_BAR_COLOR")
730 | centerBarColor = bundle.getInt("CENTER_BAR_COLOR")
731 | transitionBarColor = bundle.getInt("TRANSITION_BAR_COLOR")
732 | isGradientNeed = bundle.getBoolean("IS_GRADIENT_NEED")
733 | invalidate()
734 | }
735 |
736 | interface OnRangeSeekBarPostListener {
737 | fun onValuesChanged(minValue: Float, maxValue: Float)
738 | fun onValuesChanged(minValue: Int, maxValue: Int)
739 | }
740 |
741 | interface OnRangeSeekBarRealTimeListener {
742 | fun onValuesChanging(minValue: Float, maxValue: Float)
743 | fun onValuesChanging(minValue: Int, maxValue: Int)
744 | }
745 |
746 | data class Range(val leftValue: Float, val rightValue: Float)
747 | data class RangeInfo(val minValue: Float, val maxValue: Float, val stepValue: Float)
748 |
749 | private enum class Thumb {
750 | MIN, MAX
751 | }
752 | }
--------------------------------------------------------------------------------