├── 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 | 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 | 6 | 7 | 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 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | [![Platform](https://img.shields.io/badge/platform-Android-yellow.svg)](https://www.android.com) 2 | [![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21) 3 | [ ![Download](https://api.bintray.com/packages/rizlee/rangeseekbar/rangeseekbar/images/download.svg?version=1.0.0) ](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 | ![alt text](example_2.png) 44 | 45 | ![alt text](example.png) 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 | } --------------------------------------------------------------------------------