diff --git a/Range-slider-ios.gif b/Range-slider-ios.gif new file mode 100644 index 0000000..8a72cab Binary files /dev/null and b/Range-slider-ios.gif differ diff --git a/android/build.gradle b/android/build.gradle index 0cc9ba5..4eb608c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,6 +9,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' // noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } @@ -16,6 +18,7 @@ buildscript { apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'com.github.dcendents.android-maven' def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeRangeSlider_' + name] @@ -127,5 +130,6 @@ dependencies { // noinspection GradleDynamicVersion api 'com.facebook.react:react-native:+' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - compile 'com.crystal:crystalrangeseekbar:1.1.3' + compile project(':rangeseekbar') + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } diff --git a/android/crystalrangeseekbar/.gitignore b/android/crystalrangeseekbar/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/android/crystalrangeseekbar/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/crystalrangeseekbar/build.gradle b/android/crystalrangeseekbar/build.gradle new file mode 100644 index 0000000..3722e15 --- /dev/null +++ b/android/crystalrangeseekbar/build.gradle @@ -0,0 +1,59 @@ +apply plugin: 'com.android.library' + +ext { + // Where you will see your artifact in Bintray's web interface + // The "bintrayName" should match the name of the Bintray repro. + bintrayRepo = 'maven' + bintrayName = 'crystalrangeseekbar' + + // Maven metadata + publishedGroupId = 'com.crystal' + libraryName = 'CrystalRangeSeekbar' + // Save yourself a head ache, and set this equal to the name of the Android Studio library + // module. The artifact name needs to match the name of the library. + artifact = 'crystalrangeseekbar' + + libraryDescription = 'An extended version of android range seekbar.' + + gitUrl = 'https://github.com/syedowaisali/crystal-range-seekbar.git' + + libraryVersion = '1.1.4' + + developerId = 'syedowaisali' + developerName = 'Syed Owais Ali' + developerEmail = 'dp.owaisali@gmail.com' + + licenseName = 'The Apache Software License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + allLicenses = ["Apache-2.0"] +} + +android { + compileSdkVersion 27 + buildToolsVersion '27.0.3' + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:27.0.2' +} + +repositories { + google() +} + +apply from: 'https://raw.githubusercontent.com/attwellBrian/JCenter/master/installv1.gradle' +apply from: 'https://raw.githubusercontent.com/attwellBrian/JCenter/master/bintrayv1.gradle' diff --git a/android/crystalrangeseekbar/crystalrangeseekbar-rangeseekbar.iml b/android/crystalrangeseekbar/crystalrangeseekbar-rangeseekbar.iml new file mode 100644 index 0000000..084762a --- /dev/null +++ b/android/crystalrangeseekbar/crystalrangeseekbar-rangeseekbar.iml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/crystalrangeseekbar/proguard-rules.pro b/android/crystalrangeseekbar/proguard-rules.pro new file mode 100644 index 0000000..01eea0a --- /dev/null +++ b/android/crystalrangeseekbar/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/crystalrangeseekbar/rangeseekbar.iml b/android/crystalrangeseekbar/rangeseekbar.iml new file mode 100644 index 0000000..4e846e8 --- /dev/null +++ b/android/crystalrangeseekbar/rangeseekbar.iml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/crystalrangeseekbar/src/androidTest/java/com/example/crystalrangeseekbar/ApplicationTest.java b/android/crystalrangeseekbar/src/androidTest/java/com/example/crystalrangeseekbar/ApplicationTest.java new file mode 100644 index 0000000..bd51c74 --- /dev/null +++ b/android/crystalrangeseekbar/src/androidTest/java/com/example/crystalrangeseekbar/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.example.crystalrangeseekbar; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/android/crystalrangeseekbar/src/main/AndroidManifest.xml b/android/crystalrangeseekbar/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c877a02 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarChangeListener.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarChangeListener.java new file mode 100644 index 0000000..f03d22b --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarChangeListener.java @@ -0,0 +1,8 @@ +package com.crystal.crystalrangeseekbar.interfaces; + +/** + * Created by owais.ali on 7/14/2016. + */ +public interface OnRangeSeekbarChangeListener { + void valueChanged(Number minValue, Number maxValue); +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarFinalValueListener.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarFinalValueListener.java new file mode 100644 index 0000000..a22e850 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnRangeSeekbarFinalValueListener.java @@ -0,0 +1,8 @@ +package com.crystal.crystalrangeseekbar.interfaces; + +/** + * Created by owais.ali on 7/14/2016. + */ +public interface OnRangeSeekbarFinalValueListener { + void finalValue(Number minValue, Number maxValue); +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarChangeListener.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarChangeListener.java new file mode 100644 index 0000000..8ac23f8 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarChangeListener.java @@ -0,0 +1,8 @@ +package com.crystal.crystalrangeseekbar.interfaces; + +/** + * Created by owais.ali on 7/14/2016. + */ +public interface OnSeekbarChangeListener { + void valueChanged(Number value); +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarFinalValueListener.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarFinalValueListener.java new file mode 100644 index 0000000..b598fce --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/interfaces/OnSeekbarFinalValueListener.java @@ -0,0 +1,8 @@ +package com.crystal.crystalrangeseekbar.interfaces; + +/** + * Created by owais.ali on 7/14/2016. + */ +public interface OnSeekbarFinalValueListener { + void finalValue(Number value); +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbRangeSeekbar.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbRangeSeekbar.java new file mode 100644 index 0000000..56c1098 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbRangeSeekbar.java @@ -0,0 +1,352 @@ +package com.crystal.crystalrangeseekbar.widgets; + +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.animation.OvershootInterpolator; + +import com.example.crystalrangeseekbar.R; + +/** + * Created by owais.ali on 7/12/2016. + */ +public class BubbleThumbRangeSeekbar extends CrystalRangeSeekbar { + + ////////////////////////////////////////// + // PRIVATE CONSTANTS + ////////////////////////////////////////// + + //private static final float BUBBLE_WITH = 200f; + //private static final float BUBBLE_HEIGHT = 200f; + + ////////////////////////////////////////// + // PRIVATE VAR + ////////////////////////////////////////// + + private boolean animate; + private boolean isPressedLeftThumb; + private boolean isPressedRightThumb; + private BubbleRect thumbPressedRect; + + ////////////////////////////////////////// + // CONSTRUCTOR + ////////////////////////////////////////// + + public BubbleThumbRangeSeekbar(Context context) { + super(context); + } + + public BubbleThumbRangeSeekbar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BubbleThumbRangeSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + ////////////////////////////////////////// + // INITIALIZATION + ////////////////////////////////////////// + + @Override + protected void init(){ + thumbPressedRect = new BubbleRect(); + super.init(); + } + + ////////////////////////////////////////// + // OVERRIDE METHODS + ////////////////////////////////////////// + + @Override + protected void touchDown(float x, float y) { + super.touchDown(x, y); + + animate = true; + if(Thumb.MIN.equals(getPressedThumb())){ + isPressedLeftThumb = true; + startAnimationUp(Thumb.MIN); + } + else if(Thumb.MAX.equals(getPressedThumb())){ + isPressedRightThumb = true; + startAnimationUp(Thumb.MAX); + } + } + + @Override + protected void touchUp(float x, float y) { + super.touchUp(x, y); + + animate = true; + if(Thumb.MIN.equals(getPressedThumb())){ + startAnimationDown(Thumb.MIN); + } + else{ + startAnimationDown(Thumb.MAX); + } + } + + @Override + protected void drawLeftThumbWithColor(Canvas canvas, Paint paint, RectF rect) { + if(isPressedLeftThumb){ + + if(! animate){ + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + rect.right = rect.left + getBubbleWith(); + rect.top = getLeftThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.bottom = getLeftThumbRect().bottom + ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + } + else{ + + rect.left = thumbPressedRect.left; + rect.right = thumbPressedRect.right; + rect.top = thumbPressedRect.top; + rect.bottom = thumbPressedRect.bottom; + } + + canvas.drawOval(rect, paint); + } + else { + canvas.drawOval(rect, paint); + } + } + + @Override + protected void drawRightThumbWithColor(Canvas canvas, Paint paint, RectF rect) { + if(isPressedRightThumb){ + + if(! animate){ + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + rect.right = rect.left + getBubbleWith(); + rect.top = getRightThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.bottom = getRightThumbRect().bottom + ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + } + else{ + + rect.left = thumbPressedRect.left; + rect.right = thumbPressedRect.right; + rect.top = thumbPressedRect.top; + rect.bottom = thumbPressedRect.bottom; + } + + canvas.drawOval(rect, paint); + } + else { + canvas.drawOval(rect, paint); + } + } + + @Override + protected void drawLeftThumbWithImage(Canvas canvas, Paint paint, RectF rect, Bitmap image) { + if(isPressedLeftThumb){ + + if(! animate){ + image = resizeImage((int) getBubbleWith(), (int) getBubbleHeight(), image); + rect.top = getLeftThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + } + else{ + image = resizeImage((int) thumbPressedRect.imageWith, (int) thumbPressedRect.imageHeight, image); + rect.top = thumbPressedRect.top; + rect.left = thumbPressedRect.left; + } + + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + else{ + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + } + + @Override + protected void drawRightThumbWithImage(Canvas canvas, Paint paint, RectF rect, Bitmap image) { + if(isPressedRightThumb){ + + if(! animate){ + image = resizeImage((int) getBubbleWith(), (int) getBubbleHeight(), image); + rect.top = getRightThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + } + else{ + image = resizeImage((int) thumbPressedRect.imageWith, (int) thumbPressedRect.imageHeight, image); + rect.top = thumbPressedRect.top; + rect.left = thumbPressedRect.left; + } + + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + else{ + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + } + + ////////////////////////////////////////// + // PROTECTED METHODS + ////////////////////////////////////////// + + protected float getBubbleWith(){ + return getResources().getDimension(R.dimen.bubble_thumb_width); + } + + protected float getBubbleHeight(){ + return getResources().getDimension(R.dimen.bubble_thumb_height); + } + + protected void startAnimationUp(final Thumb thumb){ + + BubbleRect toRect = new BubbleRect(); + RectF fromRect; + + // left thumb + if(Thumb.MIN.equals(thumb)){ + fromRect = getLeftThumbRect(); + } + else{ + fromRect = getRightThumbRect(); + } + + toRect.left = fromRect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + toRect.right = toRect.left + getBubbleWith(); + toRect.top = fromRect.top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + toRect.bottom = fromRect.bottom + ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + + PropertyValuesHolder leftValueHolder = PropertyValuesHolder.ofFloat("left", fromRect.left, toRect.left); + PropertyValuesHolder rightValueHolder = PropertyValuesHolder.ofFloat("right", fromRect.right, toRect.right); + PropertyValuesHolder topValueHolder = PropertyValuesHolder.ofFloat("top", fromRect.top, toRect.top); + PropertyValuesHolder bottomValueHolder = PropertyValuesHolder.ofFloat("bottom", fromRect.bottom, toRect.bottom); + PropertyValuesHolder imageWithValueHolder = PropertyValuesHolder.ofFloat("width", getThumbWidth(), getBubbleWith()); + PropertyValuesHolder imageHeightValueHolder = PropertyValuesHolder.ofFloat("height", getThumbHeight(), getBubbleHeight()); + + ValueAnimator animation = ValueAnimator.ofPropertyValuesHolder(leftValueHolder, rightValueHolder, topValueHolder, bottomValueHolder, imageWithValueHolder, imageHeightValueHolder); + animation.setDuration(200); + animation.setInterpolator(new OvershootInterpolator(5f)); + animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + thumbPressedRect.left = (float)animation.getAnimatedValue("left"); + thumbPressedRect.right = (float)animation.getAnimatedValue("right"); + thumbPressedRect.top = (float)animation.getAnimatedValue("top"); + thumbPressedRect.bottom = (float)animation.getAnimatedValue("bottom"); + thumbPressedRect.imageWith = (float)animation.getAnimatedValue("width"); + thumbPressedRect.imageHeight = (float)animation.getAnimatedValue("height"); + invalidate(); + } + }); + animation.start(); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + animate = false; + } + }, 200); + } + + protected void startAnimationDown(Thumb thumb){ + + RectF toRect = new RectF(); + RectF fromRect; + + // left thumb + if(Thumb.MIN.equals(thumb)){ + fromRect = getLeftThumbRect(); + } + else{ + fromRect = getRightThumbRect(); + } + + toRect.left = fromRect.left + ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + toRect.right = toRect.left + getThumbWidth(); + toRect.top = 0f; + toRect.bottom = getThumbHeight(); + + PropertyValuesHolder leftValueHolder = PropertyValuesHolder.ofFloat("left", fromRect.left, toRect.left); + PropertyValuesHolder rightValueHolder = PropertyValuesHolder.ofFloat("right", fromRect.right, toRect.right); + PropertyValuesHolder topValueHolder = PropertyValuesHolder.ofFloat("top", fromRect.top, toRect.top); + PropertyValuesHolder bottomValueHolder = PropertyValuesHolder.ofFloat("bottom", fromRect.bottom, toRect.bottom); + PropertyValuesHolder imageWithValueHolder = PropertyValuesHolder.ofFloat("width", getBubbleWith(), getThumbWidth()); + PropertyValuesHolder imageHeightValueHolder = PropertyValuesHolder.ofFloat("height", getBubbleHeight(), getThumbHeight()); + + ValueAnimator animation = ValueAnimator.ofPropertyValuesHolder(leftValueHolder, rightValueHolder, topValueHolder, bottomValueHolder, imageWithValueHolder, imageHeightValueHolder); + animation.setDuration(300); + animation.setInterpolator(new OvershootInterpolator(3f)); + animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + thumbPressedRect.left = (float)animation.getAnimatedValue("left"); + thumbPressedRect.right = (float)animation.getAnimatedValue("right"); + thumbPressedRect.top = (float)animation.getAnimatedValue("top"); + thumbPressedRect.bottom = (float)animation.getAnimatedValue("bottom"); + thumbPressedRect.imageWith = (float)animation.getAnimatedValue("width"); + thumbPressedRect.imageHeight = (float)animation.getAnimatedValue("height"); + invalidate(); + } + }); + animation.start(); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + animate = false; + isPressedLeftThumb = false; + isPressedRightThumb = false; + } + }, 300); + } + + ////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////// + + + ////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////// + + private Bitmap resizeImage( int iconWidth, int iconHeight, Bitmap bmp) { + + + int width = bmp.getWidth(); + int height = bmp.getHeight(); + + // calculate the scale + float scaleWidth = ((float) iconWidth) / width; + float scaleHeight = ((float) iconHeight) / height; + + // create a matrix for the manipulation + Matrix matrix = new Matrix(); + // resize the Bitmap + matrix.postScale(scaleWidth, scaleHeight); + + // if you want to rotate the Bitmap + // matrix.postRotate(45); + + // recreate the new Bitmap + + // make a Drawable from Bitmap to allow to set the Bitmap + // to the ImageView, ImageButton or what ever + return Bitmap.createBitmap(bmp, 0, 0, width, height, matrix, true); + + } + + ////////////////////////////////////////// + // PRIVATE CLASS + ////////////////////////////////////////// + + private class BubbleRect{ + public float left; + public float right; + public float top; + public float bottom; + public float imageWith; + public float imageHeight; + } + +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbSeekbar.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbSeekbar.java new file mode 100644 index 0000000..012e090 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/BubbleThumbSeekbar.java @@ -0,0 +1,280 @@ +package com.crystal.crystalrangeseekbar.widgets; + +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.animation.OvershootInterpolator; + +import com.example.crystalrangeseekbar.R; + +/** + * Created by owais.ali on 7/12/2016. + */ +public class BubbleThumbSeekbar extends CrystalSeekbar { + + ////////////////////////////////////////// + // PRIVATE CONSTANTS + ////////////////////////////////////////// + + //private static final float BUBBLE_WITH = 200f; + //private static final float BUBBLE_HEIGHT = 200f; + + ////////////////////////////////////////// + // PRIVATE VAR + ////////////////////////////////////////// + + private boolean animate; + private boolean isPressedLeftThumb; + private BubbleRect thumbPressedRect; + + ////////////////////////////////////////// + // CONSTRUCTOR + ////////////////////////////////////////// + + public BubbleThumbSeekbar(Context context) { + super(context); + } + + public BubbleThumbSeekbar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BubbleThumbSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + ////////////////////////////////////////// + // INITIALIZATION + ////////////////////////////////////////// + + @Override + protected void init(){ + thumbPressedRect = new BubbleRect(); + super.init(); + } + + ////////////////////////////////////////// + // OVERRIDE METHODS + ////////////////////////////////////////// + + @Override + protected void touchDown(float x, float y) { + super.touchDown(x, y); + + animate = true; + if(Thumb.MIN.equals(getPressedThumb())){ + isPressedLeftThumb = true; + startAnimationUp(); + } + } + + @Override + protected void touchUp(float x, float y) { + super.touchUp(x, y); + + animate = true; + if(Thumb.MIN.equals(getPressedThumb())){ + startAnimationDown(); + } + } + + @Override + protected void drawLeftThumbWithColor(Canvas canvas, Paint paint, RectF rect) { + if(isPressedLeftThumb){ + + if(! animate){ + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + rect.right = rect.left + getBubbleWith(); + rect.top = getThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.bottom = getThumbRect().bottom + ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + } + else{ + + rect.left = thumbPressedRect.left; + rect.right = thumbPressedRect.right; + rect.top = thumbPressedRect.top; + rect.bottom = thumbPressedRect.bottom; + } + + canvas.drawOval(rect, paint); + } + else { + canvas.drawOval(rect, paint); + } + } + + @Override + protected void drawLeftThumbWithImage(Canvas canvas, Paint paint, RectF rect, Bitmap image) { + if(isPressedLeftThumb){ + + if(! animate){ + image = resizeImage((int) getBubbleWith(), (int) getBubbleHeight(), image); + rect.top = getThumbRect().top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + rect.left = rect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + } + else{ + image = resizeImage((int) thumbPressedRect.imageWith, (int) thumbPressedRect.imageHeight, image); + rect.top = thumbPressedRect.top; + rect.left = thumbPressedRect.left; + } + + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + else{ + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + } + + ////////////////////////////////////////// + // PROTECTED METHODS + ////////////////////////////////////////// + + protected float getBubbleWith(){ + return getResources().getDimension(R.dimen.bubble_thumb_width); + } + + protected float getBubbleHeight(){ + return getResources().getDimension(R.dimen.bubble_thumb_height); + } + + protected void startAnimationUp(){ + + BubbleRect toRect = new BubbleRect(); + RectF fromRect = getThumbRect(); + + toRect.left = fromRect.left - ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + toRect.right = toRect.left + getBubbleWith(); + toRect.top = fromRect.top - ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + toRect.bottom = fromRect.bottom + ((getBubbleHeight() / 2) - (getThumbHeight() / 2)); + + PropertyValuesHolder leftValueHolder = PropertyValuesHolder.ofFloat("left", fromRect.left, toRect.left); + PropertyValuesHolder rightValueHolder = PropertyValuesHolder.ofFloat("right", fromRect.right, toRect.right); + PropertyValuesHolder topValueHolder = PropertyValuesHolder.ofFloat("top", fromRect.top, toRect.top); + PropertyValuesHolder bottomValueHolder = PropertyValuesHolder.ofFloat("bottom", fromRect.bottom, toRect.bottom); + PropertyValuesHolder imageWithValueHolder = PropertyValuesHolder.ofFloat("width", getThumbWidth(), getBubbleWith()); + PropertyValuesHolder imageHeightValueHolder = PropertyValuesHolder.ofFloat("height", getThumbHeight(), getBubbleHeight()); + + ValueAnimator animation = ValueAnimator.ofPropertyValuesHolder(leftValueHolder, rightValueHolder, topValueHolder, bottomValueHolder, imageWithValueHolder, imageHeightValueHolder); + animation.setDuration(200); + animation.setInterpolator(new OvershootInterpolator(5f)); + animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + thumbPressedRect.left = (float)animation.getAnimatedValue("left"); + thumbPressedRect.right = (float)animation.getAnimatedValue("right"); + thumbPressedRect.top = (float)animation.getAnimatedValue("top"); + thumbPressedRect.bottom = (float)animation.getAnimatedValue("bottom"); + thumbPressedRect.imageWith = (float)animation.getAnimatedValue("width"); + thumbPressedRect.imageHeight = (float)animation.getAnimatedValue("height"); + invalidate(); + } + }); + animation.start(); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + animate = false; + } + }, 200); + } + + protected void startAnimationDown(){ + + RectF toRect = new RectF(); + RectF fromRect = getThumbRect(); + + toRect.left = fromRect.left + ((getBubbleWith() / 2) - (getThumbWidth() / 2)); + toRect.right = toRect.left + getThumbWidth(); + toRect.top = 0f; + toRect.bottom = getThumbHeight(); + + PropertyValuesHolder leftValueHolder = PropertyValuesHolder.ofFloat("left", fromRect.left, toRect.left); + PropertyValuesHolder rightValueHolder = PropertyValuesHolder.ofFloat("right", fromRect.right, toRect.right); + PropertyValuesHolder topValueHolder = PropertyValuesHolder.ofFloat("top", fromRect.top, toRect.top); + PropertyValuesHolder bottomValueHolder = PropertyValuesHolder.ofFloat("bottom", fromRect.bottom, toRect.bottom); + PropertyValuesHolder imageWithValueHolder = PropertyValuesHolder.ofFloat("width", getBubbleWith(), getThumbWidth()); + PropertyValuesHolder imageHeightValueHolder = PropertyValuesHolder.ofFloat("height", getBubbleHeight(), getThumbHeight()); + + ValueAnimator animation = ValueAnimator.ofPropertyValuesHolder(leftValueHolder, rightValueHolder, topValueHolder, bottomValueHolder, imageWithValueHolder, imageHeightValueHolder); + animation.setDuration(300); + animation.setInterpolator(new OvershootInterpolator(3f)); + animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + thumbPressedRect.left = (float)animation.getAnimatedValue("left"); + thumbPressedRect.right = (float)animation.getAnimatedValue("right"); + thumbPressedRect.top = (float)animation.getAnimatedValue("top"); + thumbPressedRect.bottom = (float)animation.getAnimatedValue("bottom"); + thumbPressedRect.imageWith = (float)animation.getAnimatedValue("width"); + thumbPressedRect.imageHeight = (float)animation.getAnimatedValue("height"); + invalidate(); + } + }); + animation.start(); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + animate = false; + isPressedLeftThumb = false; + } + }, 300); + } + + ////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////// + + + ////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////// + + private Bitmap resizeImage( int iconWidth, int iconHeight, Bitmap bmp) { + + + int width = bmp.getWidth(); + int height = bmp.getHeight(); + + // calculate the scale + float scaleWidth = ((float) iconWidth) / width; + float scaleHeight = ((float) iconHeight) / height; + + // create a matrix for the manipulation + Matrix matrix = new Matrix(); + // resize the Bitmap + matrix.postScale(scaleWidth, scaleHeight); + + // if you want to rotate the Bitmap + // matrix.postRotate(45); + + // recreate the new Bitmap + + // make a Drawable from Bitmap to allow to set the Bitmap + // to the ImageView, ImageButton or what ever + return Bitmap.createBitmap(bmp, 0, 0, width, height, matrix, true); + + } + + ////////////////////////////////////////// + // PRIVATE CLASS + ////////////////////////////////////////// + + private class BubbleRect{ + public float left; + public float right; + public float top; + public float bottom; + public float imageWith; + public float imageHeight; + } + +} diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalRangeSeekbar.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalRangeSeekbar.java new file mode 100644 index 0000000..57ec5c4 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalRangeSeekbar.java @@ -0,0 +1,1169 @@ +package com.crystal.crystalrangeseekbar.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.crystal.crystalrangeseekbar.interfaces.OnRangeSeekbarChangeListener; +import com.crystal.crystalrangeseekbar.interfaces.OnRangeSeekbarFinalValueListener; +import com.example.crystalrangeseekbar.R; + + +/** + * Created by owais.ali on 6/20/2016. + */ +public class CrystalRangeSeekbar extends View { + + ////////////////////////////////////////// + // PRIVATE CONSTANTS + ////////////////////////////////////////// + + private static final int INVALID_POINTER_ID = 255; + //private static final int DEFAULT_THUMB_WIDTH = 80; + //private static final int DEFAULT_THUMB_HEIGHT = 80; + + private final float NO_STEP = -1f; + private final float NO_FIXED_GAP = -1f; + + ////////////////////////////////////////// + // PUBLIC CONSTANTS CLASS + ////////////////////////////////////////// + + public static final class DataType{ + public static final int LONG = 0; + public static final int DOUBLE = 1; + public static final int INTEGER = 2; + public static final int FLOAT = 3; + public static final int SHORT = 4; + public static final int BYTE = 5; + } + + public static final class ColorMode { + public static final int SOLID = 0; + public static final int GRADIENT = 1; + } + + ////////////////////////////////////////// + // PRIVATE VAR + ////////////////////////////////////////// + + private OnRangeSeekbarChangeListener onRangeSeekbarChangeListener; + private OnRangeSeekbarFinalValueListener onRangeSeekbarFinalValueListener; + + private float absoluteMinValue; + private float absoluteMaxValue; + private float absoluteMinStartValue; + private float absoluteMaxStartValue; + private float minValue; + private float maxValue; + private float minStartValue; + private float maxStartValue; + private float steps; + private float gap; + private float fixGap; + + private int mActivePointerId = INVALID_POINTER_ID; + + private int dataType; + private float cornerRadius; + private int barColorMode; + private int barColor; + private int barGradientStart; + private int barGradientEnd; + private int barHighlightColorMode; + private int barHighlightColor; + private int barHighlightGradientStart; + private int barHighlightGradientEnd; + private int leftThumbColor; + private int rightThumbColor; + private int leftThumbColorNormal; + private int leftThumbColorPressed; + private int rightThumbColorNormal; + private int rightThumbColorPressed; + private boolean seekBarTouchEnabled; + private float barPadding; + private float barHeight; + private float _barHeight; + private float thumbWidth = getResources().getDimension(R.dimen.thumb_width); + private float thumbDiameter; + + //private float thumbHalfWidth; + //private float thumbHalfHeight; + private float thumbHeight = getResources().getDimension(R.dimen.thumb_height); + private Drawable leftDrawable; + private Drawable rightDrawable; + private Drawable leftDrawablePressed; + private Drawable rightDrawablePressed; + private Bitmap leftThumb; + private Bitmap leftThumbPressed; + private Bitmap rightThumb; + private Bitmap rightThumbPressed; + private Thumb pressedThumb; + private double normalizedMinValue = 0d; + private double normalizedMaxValue = 100d; + private int pointerIndex; + private RectF _rect; + private Paint _paint; + + private RectF rectLeftThumb, rectRightThumb; + + private boolean mIsDragging; + + ////////////////////////////////////////// + // ENUMERATION + ////////////////////////////////////////// + + protected enum Thumb{ MIN, MAX } + + ////////////////////////////////////////// + // CONSTRUCTOR + ////////////////////////////////////////// + + public CrystalRangeSeekbar(Context context) { + this(context, null); + } + + public CrystalRangeSeekbar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CrystalRangeSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + // prevent render is in edit mode + if(isInEditMode()) return; + + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CrystalRangeSeekbar); + try{ + cornerRadius = getCornerRadius(array); + minValue = getMinValue(array); + maxValue = getMaxValue(array); + minStartValue = getMinStartValue(array); + maxStartValue = getMaxStartValue(array); + steps = getSteps(array); + gap = getGap(array); + fixGap = getFixedGap(array); + _barHeight = getBarHeight(array); + barColorMode = getBarColorMode(array); + barColor = getBarColor(array); + barGradientStart = getBarGradientStart(array); + barGradientEnd = getBarGradientEnd(array); + barHighlightColorMode = getBarHighlightColorMode(array); + barHighlightColor = getBarHighlightColor(array); + barHighlightGradientStart = getBarHighlightGradientStart(array); + barHighlightGradientEnd = getBarHighlightGradientEnd(array); + leftThumbColorNormal = getLeftThumbColor(array); + rightThumbColorNormal = getRightThumbColor(array); + leftThumbColorPressed = getLeftThumbColorPressed(array); + rightThumbColorPressed = getRightThumbColorPressed(array); + leftDrawable = getLeftDrawable(array); + rightDrawable = getRightDrawable(array); + leftDrawablePressed = getLeftDrawablePressed(array); + rightDrawablePressed = getRightDrawablePressed(array); + thumbDiameter = getDiameter(array); + dataType = getDataType(array); + seekBarTouchEnabled = isSeekBarTouchEnabled(array); + } + finally { + array.recycle(); + } + + init(); + } + + ////////////////////////////////////////// + // INITIALIZING + ////////////////////////////////////////// + + protected void init(){ + absoluteMinValue = minValue; + absoluteMaxValue = maxValue; + leftThumbColor = leftThumbColorNormal; + rightThumbColor = rightThumbColorNormal; + leftThumb = getBitmap(leftDrawable); + rightThumb = getBitmap(rightDrawable); + leftThumbPressed = getBitmap(leftDrawablePressed); + rightThumbPressed = getBitmap(rightDrawablePressed); + leftThumbPressed = (leftThumbPressed == null) ? leftThumb : leftThumbPressed; + rightThumbPressed = (rightThumbPressed == null) ? rightThumb : rightThumbPressed; + + gap = Math.max(0, Math.min(gap, absoluteMaxValue - absoluteMinValue)); + gap = gap / (absoluteMaxValue - absoluteMinValue) * 100; + if(fixGap != NO_FIXED_GAP){ + fixGap = Math.min(fixGap, absoluteMaxValue); + fixGap = fixGap / (absoluteMaxValue - absoluteMinValue) * 100; + addFixGap(true); + } + + thumbWidth = getThumbWidth(); + thumbHeight = getThumbHeight(); + + //thumbHalfWidth = thumbWidth / 2; + //thumbHalfHeight = thumbHeight / 2; + + barHeight = getBarHeight(); + barPadding = getBarPadding(); + + _paint = new Paint(Paint.ANTI_ALIAS_FLAG); + _rect = new RectF(); + rectLeftThumb = new RectF(); + rectRightThumb = new RectF(); + + pressedThumb = null; + + setMinStartValue(); + setMaxStartValue(); + + setWillNotDraw(false); + } + + ////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////// + + public CrystalRangeSeekbar setCornerRadius(float cornerRadius){ + this.cornerRadius = cornerRadius; + return this; + } + + public CrystalRangeSeekbar setMinValue(float minValue){ + this.minValue = minValue; + this.absoluteMinValue = minValue; + return this; + } + + public CrystalRangeSeekbar setMaxValue(float maxValue){ + this.maxValue = maxValue; + this.absoluteMaxValue = maxValue; + return this; + } + + public CrystalRangeSeekbar setMinStartValue(float minStartValue){ + this.minStartValue = minStartValue; + this.absoluteMinStartValue = minStartValue; + return this; + } + + public CrystalRangeSeekbar setMaxStartValue(float maxStartValue){ + this.maxStartValue = maxStartValue; + this.absoluteMaxStartValue = maxStartValue; + return this; + } + + public CrystalRangeSeekbar setSteps(float steps){ + this.steps = steps; + return this; + } + + public CrystalRangeSeekbar setGap(float gap){ + this.gap = gap; + return this; + } + + public CrystalRangeSeekbar setFixGap(float fixGap){ + this.fixGap = fixGap; + return this; + } + + public CrystalRangeSeekbar setBarHeight(float height) { + _barHeight = height; + return this; + } + + public CrystalRangeSeekbar setBarColorMode(int barColorMode) { + this.barColorMode = barColorMode; + return this; + } + + public CrystalRangeSeekbar setBarColor(int barColor) { + this.barColor = barColor; + return this; + } + + public CrystalRangeSeekbar setBarGradientStart(int barGradientStart) { + this.barGradientStart = barGradientStart; + return this; + } + + public CrystalRangeSeekbar setBarGradientEnd(int barGradientEnd) { + this.barGradientEnd = barGradientEnd; + return this; + } + + public CrystalRangeSeekbar setBarHighlightColorMode(int barHighlightColorMode) { + this.barHighlightColorMode = barHighlightColorMode; + return this; + } + + public CrystalRangeSeekbar setBarHighlightColor(int barHighlightColor) { + this.barHighlightColor = barHighlightColor; + return this; + } + + public CrystalRangeSeekbar setBarHighlightGradientStart(int barHighlightGradientStart) { + this.barHighlightGradientStart = barHighlightGradientStart; + return this; + } + + public CrystalRangeSeekbar setBarHighlightGradientEnd(int barHighlightGradientEnd) { + this.barHighlightGradientEnd = barHighlightGradientEnd; + return this; + } + + public CrystalRangeSeekbar setLeftThumbColor(int leftThumbColorNormal){ + this.leftThumbColorNormal = leftThumbColorNormal; + return this; + } + + public CrystalRangeSeekbar setLeftThumbHighlightColor(int leftThumbColorPressed){ + this.leftThumbColorPressed = leftThumbColorPressed; + return this; + } + + public CrystalRangeSeekbar setLeftThumbDrawable(int resId){ + setLeftThumbDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalRangeSeekbar setLeftThumbDrawable(Drawable drawable){ + setLeftThumbBitmap(getBitmap(drawable)); + return this; + } + + public CrystalRangeSeekbar setLeftThumbBitmap(Bitmap bitmap){ + leftThumb = bitmap; + return this; + } + + public CrystalRangeSeekbar setLeftThumbHighlightDrawable(int resId){ + setLeftThumbHighlightDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalRangeSeekbar setLeftThumbHighlightDrawable(Drawable drawable){ + setLeftThumbHighlightBitmap(getBitmap(drawable)); + return this; + } + + public CrystalRangeSeekbar setLeftThumbHighlightBitmap(Bitmap bitmap){ + leftThumbPressed = bitmap; + return this; + } + + public CrystalRangeSeekbar setRightThumbColor(int rightThumbColorNormal){ + this.rightThumbColorNormal = rightThumbColorNormal; + return this; + } + + public CrystalRangeSeekbar setRightThumbHighlightColor(int rightThumbColorPressed){ + this.rightThumbColorPressed = rightThumbColorPressed; + return this; + } + + public CrystalRangeSeekbar setRightThumbDrawable(int resId){ + setRightThumbDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalRangeSeekbar setRightThumbDrawable(Drawable drawable){ + setRightThumbBitmap(getBitmap(drawable)); + return this; + } + + public CrystalRangeSeekbar setRightThumbBitmap(Bitmap bitmap){ + rightThumb = bitmap; + return this; + } + + public CrystalRangeSeekbar setRightThumbHighlightDrawable(int resId){ + setRightThumbHighlightDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalRangeSeekbar setRightThumbHighlightDrawable(Drawable drawable){ + setRightThumbHighlightBitmap(getBitmap(drawable)); + return this; + } + + public CrystalRangeSeekbar setRightThumbHighlightBitmap(Bitmap bitmap){ + rightThumbPressed = bitmap; + return this; + } + + public CrystalRangeSeekbar setDataType(int dataType){ + this.dataType = dataType; + return this; + } + + + /* add support to customize thumb size */ + + public CrystalRangeSeekbar setThumbSize(float thumbSize) { + final float scale = getContext().getResources().getDisplayMetrics().density; + this.thumbHeight = thumbSize * scale + 0.5f; + this.thumbWidth = thumbSize * scale + 0.5f; + this.thumbDiameter = (thumbSize * scale + 0.5f) / 2; + return this; + } + + public void setOnRangeSeekbarChangeListener(OnRangeSeekbarChangeListener onRangeSeekbarChangeListener){ + this.onRangeSeekbarChangeListener = onRangeSeekbarChangeListener; + if(this.onRangeSeekbarChangeListener != null){ + this.onRangeSeekbarChangeListener.valueChanged(getSelectedMinValue(), getSelectedMaxValue()); + } + } + + public void setOnRangeSeekbarFinalValueListener(OnRangeSeekbarFinalValueListener onRangeSeekbarFinalValueListener){ + this.onRangeSeekbarFinalValueListener = onRangeSeekbarFinalValueListener; + } + + public Number getSelectedMinValue(){ + double nv = normalizedMinValue; + if(steps > 0 && steps <= ((Math.abs(absoluteMaxValue)) / 2)){ + float stp = steps / (absoluteMaxValue - absoluteMinValue) * 100; + double half_step = stp / 2; + double mod = nv % stp; + if(mod > half_step){ + nv = nv - mod; + nv = nv + stp; + } + else{ + nv = nv - mod; + } + } + else{ + if(steps != NO_STEP) + throw new IllegalStateException("steps out of range " + steps); + } + + return formatValue(normalizedToValue(nv)); + } + + public Number getSelectedMaxValue(){ + + double nv = normalizedMaxValue; + if(steps > 0 && steps <= (Math.abs(absoluteMaxValue) / 2)){ + float stp = steps / (absoluteMaxValue - absoluteMinValue) * 100; + double half_step = stp / 2; + double mod = nv % stp; + if(mod > half_step){ + nv = nv - mod; + nv = nv + stp; + } + else{ + nv = nv - mod; + } + } + else{ + if(steps != NO_STEP) + throw new IllegalStateException("steps out of range " + steps); + } + + return formatValue(normalizedToValue(nv)); + } + + public void apply(){ + + // reset normalize min and max value + normalizedMinValue = 0d; + normalizedMaxValue = 100d; + + gap = Math.max(0, Math.min(gap, absoluteMaxValue - absoluteMinValue)); + gap = gap / (absoluteMaxValue - absoluteMinValue) * 100; + if(fixGap != NO_FIXED_GAP){ + fixGap = Math.min(fixGap, absoluteMaxValue); + fixGap = fixGap / (absoluteMaxValue - absoluteMinValue) * 100; + addFixGap(true); + } + + thumbWidth = getThumbWidth(); + thumbHeight = getThumbHeight(); + + //thumbHalfWidth = thumbWidth / 2; + //thumbHalfHeight = thumbHeight / 2; + + barHeight = getBarHeight(); + barPadding = thumbWidth * 0.5f; + + // set min start value + if(minStartValue <= absoluteMinValue){ + minStartValue = 0; + setNormalizedMinValue(minStartValue); + } + else if(minStartValue >= absoluteMaxValue){ + minStartValue = absoluteMaxValue; + setMinStartValue(); + } + else{ + setMinStartValue(); + } + + // set max start value + if (maxStartValue < absoluteMinStartValue || maxStartValue <= absoluteMinValue) { + maxStartValue = 0; + setNormalizedMaxValue(maxStartValue); + } + else if(maxStartValue >= absoluteMaxValue){ + maxStartValue = absoluteMaxValue; + setMaxStartValue(); + } + else{ + setMaxStartValue(); + } + invalidate(); + + if (onRangeSeekbarChangeListener != null) { + onRangeSeekbarChangeListener.valueChanged(getSelectedMinValue(), getSelectedMaxValue()); + } + } + + ////////////////////////////////////////// + // PROTECTED METHODS + ////////////////////////////////////////// + + protected Thumb getPressedThumb(){ + return pressedThumb; + } + + + protected float getThumbWidth() { + return (leftThumb != null) ? leftThumb.getWidth() : thumbWidth; + } + + protected float getThumbHeight() { + return (leftThumb != null) ? leftThumb.getHeight() : thumbHeight; + } + + protected float getThumbDiameter() { + return (thumbDiameter > 0) ? thumbDiameter : thumbWidth; + } + + protected float getBarHeight(){ + return _barHeight > 0 ? _barHeight : (thumbHeight * 0.5f) * 0.3f; + } + + protected float getBarPadding(){ + return thumbWidth * 0.5f; + } + + protected Bitmap getBitmap(Drawable drawable){ + return (drawable != null) ? ((BitmapDrawable) drawable).getBitmap() : null; + } + + protected float getCornerRadius(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_corner_radius, 0f); + } + + protected float getMinValue(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_min_value, 0f); + } + + protected float getMaxValue(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_max_value, 100f); + } + + protected float getMinStartValue(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_min_start_value, minValue); + } + + protected float getMaxStartValue(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_max_start_value, maxValue); + } + + protected float getSteps(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_steps, NO_STEP); + } + + protected float getGap(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_gap, 0f); + } + + protected float getFixedGap(final TypedArray typedArray){ + return typedArray.getFloat(R.styleable.CrystalRangeSeekbar_fix_gap, NO_FIXED_GAP); + } + + protected int getBarColorMode(final TypedArray typedArray) { + return typedArray.getInt(R.styleable.CrystalRangeSeekbar_bar_color_mode, CrystalSeekbar.ColorMode.SOLID); + } + + protected float getBarHeight(final TypedArray typedArray){ + return typedArray.getDimensionPixelSize(R.styleable.CrystalRangeSeekbar_bar_height, 0); + } + + protected int getBarColor(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_color, Color.GRAY); + } + + protected int getBarGradientStart(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_gradient_start, Color.GRAY); + } + + protected int getBarGradientEnd(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_gradient_end, Color.DKGRAY); + } + + protected int getBarHighlightColorMode(final TypedArray typedArray) { + return typedArray.getInt(R.styleable.CrystalRangeSeekbar_bar_highlight_color_mode, CrystalSeekbar.ColorMode.SOLID); + } + + protected int getBarHighlightColor(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_highlight_color, Color.BLACK); + } + + protected int getBarHighlightGradientStart(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_highlight_gradient_start, Color.DKGRAY); + } + + protected int getBarHighlightGradientEnd(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_bar_highlight_gradient_end, Color.BLACK); + } + + protected int getLeftThumbColor(final TypedArray typedArray){ + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_left_thumb_color, Color.BLACK); + } + + protected int getRightThumbColor(final TypedArray typedArray){ + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_right_thumb_color, Color.BLACK); + } + + protected int getLeftThumbColorPressed(final TypedArray typedArray){ + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_left_thumb_color_pressed, Color.DKGRAY); + } + + protected int getRightThumbColorPressed(final TypedArray typedArray){ + return typedArray.getColor(R.styleable.CrystalRangeSeekbar_right_thumb_color_pressed, Color.DKGRAY); + } + + protected Drawable getLeftDrawable(final TypedArray typedArray){ + return typedArray.getDrawable(R.styleable.CrystalRangeSeekbar_left_thumb_image); + } + + protected Drawable getRightDrawable(final TypedArray typedArray){ + return typedArray.getDrawable(R.styleable.CrystalRangeSeekbar_right_thumb_image); + } + + protected Drawable getLeftDrawablePressed(final TypedArray typedArray){ + return typedArray.getDrawable(R.styleable.CrystalRangeSeekbar_left_thumb_image_pressed); + } + + protected Drawable getRightDrawablePressed(final TypedArray typedArray){ + return typedArray.getDrawable(R.styleable.CrystalRangeSeekbar_right_thumb_image_pressed); + } + + protected int getDataType(final TypedArray typedArray){ + return typedArray.getInt(R.styleable.CrystalRangeSeekbar_data_type, DataType.INTEGER); + } + + protected boolean isSeekBarTouchEnabled(final TypedArray typedArray){ + return typedArray.getBoolean(R.styleable.CrystalRangeSeekbar_seek_bar_touch_enabled, false); + } + + protected float getDiameter(final TypedArray typedArray){ + return typedArray.getDimensionPixelSize(R.styleable.CrystalRangeSeekbar_thumb_diameter, getResources().getDimensionPixelSize(R.dimen.thumb_height)); + } + + protected RectF getLeftThumbRect(){ + return rectLeftThumb; + } + + protected RectF getRightThumbRect(){ + return rectRightThumb; + } + + protected void setupBar(final Canvas canvas, final Paint paint, final RectF rect){ + rect.left = barPadding; + rect.top = 0.5f * (getHeight() - barHeight); + rect.right = getWidth() - barPadding; + rect.bottom = 0.5f * (getHeight() + barHeight); + + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + + if (barColorMode == CrystalSeekbar.ColorMode.SOLID) { + paint.setColor(barColor); + drawBar(canvas, paint, rect); + + } else { + paint.setShader( + new LinearGradient(rect.left, rect.bottom, rect.right, rect.top, + barGradientStart, + barGradientEnd, + Shader.TileMode.MIRROR) + ); + + drawBar(canvas, paint, rect); + + paint.setShader(null); + } + } + + protected void drawBar(final Canvas canvas, final Paint paint, final RectF rect){ + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint); + } + + protected void setupHighlightBar(final Canvas canvas, final Paint paint, final RectF rect){ + rect.left = normalizedToScreen(normalizedMinValue) + (getThumbWidth() / 2); + rect.right = normalizedToScreen(normalizedMaxValue) + (getThumbWidth() / 2); + + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + + if (barHighlightColorMode == CrystalSeekbar.ColorMode.SOLID) { + paint.setColor(barHighlightColor); + drawHighlightBar(canvas, paint, rect); + + } else { + paint.setShader( + new LinearGradient(rect.left, rect.bottom, rect.right, rect.top, + barHighlightGradientStart, + barHighlightGradientEnd, + Shader.TileMode.MIRROR) + ); + + drawHighlightBar(canvas, paint, rect); + + paint.setShader(null); + } + } + + protected void drawHighlightBar(final Canvas canvas, final Paint paint, final RectF rect){ + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint); + } + + protected void setupLeftThumb(final Canvas canvas, final Paint paint, final RectF rect){ + leftThumbColor = (Thumb.MIN.equals(pressedThumb)) ? leftThumbColorPressed : leftThumbColorNormal; + paint.setColor(leftThumbColor); + + //float leftL = normalizedToScreen(normalizedMinValue); + //float rightL = Math.min(leftL + thumbHalfWidth + barPadding, getWidth()); + rectLeftThumb.left = normalizedToScreen(normalizedMinValue); + rectLeftThumb.right = Math.min(rectLeftThumb.left + (getThumbWidth() / 2) + barPadding, getWidth()); + rectLeftThumb.top = 0f; + rectLeftThumb.bottom = thumbHeight; + + if(leftThumb != null){ + Bitmap lThumb = (Thumb.MIN.equals(pressedThumb)) ? leftThumbPressed : leftThumb; + drawLeftThumbWithImage(canvas, paint, rectLeftThumb, lThumb); + } + else{ + drawLeftThumbWithColor(canvas, paint, rectLeftThumb); + } + } + + protected void drawLeftThumbWithColor(final Canvas canvas, final Paint paint, final RectF rect){ + canvas.drawOval(rect, paint); + } + + protected void drawLeftThumbWithImage(final Canvas canvas, final Paint paint, final RectF rect, final Bitmap image){ + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + + protected void setupRightThumb(final Canvas canvas, final Paint paint, final RectF rect){ + + rightThumbColor = (Thumb.MAX.equals(pressedThumb)) ? rightThumbColorPressed : rightThumbColorNormal; + paint.setColor(rightThumbColor); + + //float leftR = normalizedToScreen(normalizedMaxValue); + //float rightR = Math.min(leftR + thumbHalfWidth + barPadding, getWidth()); + rectRightThumb.left = normalizedToScreen(normalizedMaxValue); + rectRightThumb.right = Math.min(rectRightThumb.left + (getThumbWidth() / 2) + barPadding, getWidth()); + rectRightThumb.top = 0f; + rectRightThumb.bottom = thumbHeight; + + if(rightThumb != null){ + Bitmap rThumb = (Thumb.MAX.equals(pressedThumb)) ? rightThumbPressed : rightThumb; + drawRightThumbWithImage(canvas, paint, rectRightThumb, rThumb); + } + else{ + drawRightThumbWithColor(canvas, paint, rectRightThumb); + } + } + + protected void drawRightThumbWithColor(final Canvas canvas, final Paint paint, final RectF rect){ + canvas.drawOval(rect, paint); + } + + protected void drawRightThumbWithImage(final Canvas canvas, final Paint paint, final RectF rect, final Bitmap image){ + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + + protected void trackTouchEvent(MotionEvent event){ + final int pointerIndex = event.findPointerIndex(mActivePointerId); + try{ + final float x = event.getX(pointerIndex); + + if (Thumb.MIN.equals(pressedThumb)) { + setNormalizedMinValue(screenToNormalized(x)); + } else if (Thumb.MAX.equals(pressedThumb)) { + setNormalizedMaxValue(screenToNormalized(x)); + } + } + catch (Exception ignored){} + } + + protected void touchDown(final float x, final float y){ + + } + + protected void touchMove(final float x, final float y){ + + } + + protected void touchUp(final float x, final float y){ + + } + + protected int getMeasureSpecWith(int widthMeasureSpec){ + int width = 200; + if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) { + width = MeasureSpec.getSize(widthMeasureSpec); + } + return width; + } + + protected int getMeasureSpecHeight(int heightMeasureSpec){ + int height = Math.round(thumbHeight); + if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) { + height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); + } + return height; + } + + protected final void log(Object object){ + Log.d("CRS=>", String.valueOf(object)); + } + + ////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////// + + private void setMinStartValue() { + if (minStartValue > minValue && minStartValue <= maxValue) { + minStartValue = Math.min(minStartValue, absoluteMaxValue); + minStartValue -= absoluteMinValue; + minStartValue = minStartValue / (absoluteMaxValue - absoluteMinValue) * 100; + setNormalizedMinValue(minStartValue); + } + } + + private void setMaxStartValue() { + if (maxStartValue <= absoluteMaxValue && maxStartValue > absoluteMinValue && maxStartValue >= absoluteMinStartValue) { + maxStartValue = Math.max(absoluteMaxStartValue, absoluteMinValue); + maxStartValue -= absoluteMinValue; + maxStartValue = maxStartValue / (absoluteMaxValue - absoluteMinValue) * 100; + setNormalizedMaxValue(maxStartValue); + } + } + + private Thumb evalPressedThumb(float touchX){ + Thumb result = null; + + boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue); + boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue); + if (minThumbPressed && maxThumbPressed) { + // if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore. + result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX; + } + else if(minThumbPressed){ + result = Thumb.MIN; + } + else if(maxThumbPressed){ + result = Thumb.MAX; + } + + if (seekBarTouchEnabled && result == null) { + result = findClosestThumb(touchX); + } + return result; + } + + private boolean isInThumbRange(float touchX, double normalizedThumbValue){ + float thumbPos = normalizedToScreen(normalizedThumbValue); + float left = thumbPos - (getThumbWidth() / 2); + float right = thumbPos + (getThumbWidth() / 2); + float x = touchX - (getThumbWidth() / 2); + if(thumbPos > (getWidth() - thumbWidth)) x = touchX; + return (x >= left && x <= right); + //return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbWidth; + } + + private Thumb findClosestThumb(float touchX) { + float screenMinX = normalizedToScreen(normalizedMinValue); + float screenMaxX = normalizedToScreen(normalizedMaxValue); + if (touchX >= screenMaxX) { + return Thumb.MAX; + } else if (touchX <= screenMinX) { + return Thumb.MIN; + } + + double minDiff = Math.abs(screenMinX - touchX); + double maxDiff = Math.abs(screenMaxX - touchX); + return minDiff < maxDiff ? Thumb.MIN : Thumb.MAX; + } + + private void onStartTrackingTouch(){ + mIsDragging = true; + } + + private void onStopTrackingTouch(){ + mIsDragging = false; + } + + private float normalizedToScreen(double normalizedCoord){ + float width = getWidth() - (barPadding * 2); + return (float) normalizedCoord / 100f * width; + } + + private double screenToNormalized(float screenCoord){ + double width = getWidth(); + + if (width <= 2 * barPadding) { + // prevent division by zero, simply return 0. + return 0d; + } else { + width = width - (barPadding * 2); + double result = screenCoord / width * 100d; + result = result - (barPadding / width * 100d); + result = Math.min(100d, Math.max(0d, result)); + return result; + + } + } + + private void setNormalizedMinValue(double value) { + normalizedMinValue = Math.max(0d, Math.min(100d, Math.min(value, normalizedMaxValue))); + if(fixGap != NO_FIXED_GAP && fixGap > 0){ + addFixGap(true); + } + else{ + addMinGap(); + } + invalidate(); + } + + private void setNormalizedMaxValue(double value) { + normalizedMaxValue = Math.max(0d, Math.min(100d, Math.max(value, normalizedMinValue))); + if(fixGap != NO_FIXED_GAP && fixGap > 0){ + addFixGap(false); + } + else{ + addMaxGap(); + } + invalidate(); + } + + private void addFixGap(boolean leftThumb){ + if(leftThumb){ + normalizedMaxValue = normalizedMinValue + fixGap; + if(normalizedMaxValue >= 100){ + normalizedMaxValue = 100; + normalizedMinValue = normalizedMaxValue - fixGap; + } + } + else{ + normalizedMinValue = normalizedMaxValue - fixGap; + if(normalizedMinValue <= 0){ + normalizedMinValue = 0; + normalizedMaxValue = normalizedMinValue + fixGap; + } + } + } + + private void addMinGap(){ + if((normalizedMinValue + gap) > normalizedMaxValue){ + double g = normalizedMinValue + gap; + normalizedMaxValue = g; + normalizedMaxValue = Math.max(0d, Math.min(100d, Math.max(g, normalizedMinValue))); + + if(normalizedMinValue >= (normalizedMaxValue - gap)){ + normalizedMinValue = normalizedMaxValue - gap; + } + } + } + + private void addMaxGap(){ + if((normalizedMaxValue - gap) < normalizedMinValue){ + double g = normalizedMaxValue - gap; + normalizedMinValue = g; + normalizedMinValue = Math.max(0d, Math.min(100d, Math.min(g, normalizedMaxValue))); + if(normalizedMaxValue <= (normalizedMinValue + gap)){ + normalizedMaxValue = normalizedMinValue + gap; + } + } + } + + private double normalizedToValue(double normalized) { + double val = normalized / 100 * (maxValue - minValue); + val = val + minValue; + return val; + } + + private void attemptClaimDrag() { + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + } + + private Number formatValue(T value) throws IllegalArgumentException{ + final Double v = (Double) value; + if (dataType == DataType.LONG) { + return v.longValue(); + } + if (dataType == DataType.DOUBLE) { + return v; + } + if (dataType == DataType.INTEGER) { + return Math.round(v); + } + if (dataType == DataType.FLOAT) { + return v.floatValue(); + } + if (dataType == DataType.SHORT) { + return v.shortValue(); + } + if (dataType == DataType.BYTE) { + return v.byteValue(); + } + throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported"); + } + + ////////////////////////////////////////// + // OVERRIDE METHODS + ////////////////////////////////////////// + + @Override + protected synchronized void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // prevent render is in edit mode + if(isInEditMode()) return; + + // setup bar + setupBar(canvas, _paint, _rect); + + // setup seek bar active range line + setupHighlightBar(canvas, _paint ,_rect); + + // draw left thumb + setupLeftThumb(canvas, _paint, _rect); + + // draw right thumb + setupRightThumb(canvas, _paint, _rect); + + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getMeasureSpecWith(widthMeasureSpec), getMeasureSpecHeight(heightMeasureSpec)); + } + + /** + * Handles thumb selection and movement. Notifies listener callback on certain events. + */ + @Override + public synchronized boolean onTouchEvent(MotionEvent event) { + + if (!isEnabled()) + return false; + + + + final int action = event.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + + case MotionEvent.ACTION_DOWN: + mActivePointerId = event.getPointerId(event.getPointerCount() - 1); + pointerIndex = event.findPointerIndex(mActivePointerId); + float mDownMotionX = event.getX(pointerIndex); + + pressedThumb = evalPressedThumb(mDownMotionX); + + if(pressedThumb == null) return super.onTouchEvent(event); + + touchDown(event.getX(pointerIndex), event.getY(pointerIndex)); + setPressed(true); + invalidate(); + onStartTrackingTouch(); + trackTouchEvent(event); + attemptClaimDrag(); + + break; + + case MotionEvent.ACTION_MOVE: + if (pressedThumb != null) { + + if (mIsDragging) { + touchMove(event.getX(pointerIndex), event.getY(pointerIndex)); + trackTouchEvent(event); + } + + if (onRangeSeekbarChangeListener != null) { + onRangeSeekbarChangeListener.valueChanged(getSelectedMinValue(), getSelectedMaxValue()); + } + } + break; + case MotionEvent.ACTION_UP: + if (mIsDragging) { + trackTouchEvent(event); + onStopTrackingTouch(); + setPressed(false); + touchUp(event.getX(pointerIndex), event.getY(pointerIndex)); + if(onRangeSeekbarFinalValueListener != null){ + onRangeSeekbarFinalValueListener.finalValue(getSelectedMinValue(), getSelectedMaxValue()); + } + } else { + // Touch up when we never crossed the touch slop threshold + // should be interpreted as a tap-seek to that location. + onStartTrackingTouch(); + trackTouchEvent(event); + onStopTrackingTouch(); + } + + pressedThumb = null; + invalidate(); + if (onRangeSeekbarChangeListener != null) { + onRangeSeekbarChangeListener.valueChanged(getSelectedMinValue(), getSelectedMaxValue()); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: { + //final int index = event.getPointerCount() - 1; + // final int index = ev.getActionIndex(); + /*mDownMotionX = event.getX(index); + mActivePointerId = event.getPointerId(index); + invalidate();*/ + break; + } + case MotionEvent.ACTION_POINTER_UP: + /*onSecondaryPointerUp(event);*/ + invalidate(); + break; + case MotionEvent.ACTION_CANCEL: + if (mIsDragging) { + onStopTrackingTouch(); + setPressed(false); + touchUp(event.getX(pointerIndex), event.getY(pointerIndex)); + } + invalidate(); // see above explanation + break; + } + + return true; + + } +} + diff --git a/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalSeekbar.java b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalSeekbar.java new file mode 100644 index 0000000..3aec70d --- /dev/null +++ b/android/crystalrangeseekbar/src/main/java/com/crystal/crystalrangeseekbar/widgets/CrystalSeekbar.java @@ -0,0 +1,978 @@ +package com.crystal.crystalrangeseekbar.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import com.crystal.crystalrangeseekbar.interfaces.OnSeekbarChangeListener; +import com.crystal.crystalrangeseekbar.interfaces.OnSeekbarFinalValueListener; +import com.example.crystalrangeseekbar.R; + + +/** + * Created by owais.ali on 6/20/2016. + */ +public class CrystalSeekbar extends View { + + ////////////////////////////////////////// + // PRIVATE CONSTANTS + ////////////////////////////////////////// + + private static final int INVALID_POINTER_ID = 255; + //private static int DEFAULT_THUMB_WIDTH ; + //private static int DEFAULT_THUMB_HEIGHT; + + private final float NO_STEP = -1f; + + ////////////////////////////////////////// + // PUBLIC CONSTANTS CLASS + ////////////////////////////////////////// + + public static final class DataType { + public static final int LONG = 0; + public static final int DOUBLE = 1; + public static final int INTEGER = 2; + public static final int FLOAT = 3; + public static final int SHORT = 4; + public static final int BYTE = 5; + } + + public static final class Position { + public static final int LEFT = 0; + public static final int RIGHT = 1; + } + + public static final class ColorMode { + public static final int SOLID = 0; + public static final int GRADIENT = 1; + } + + ////////////////////////////////////////// + // PRIVATE VAR + ////////////////////////////////////////// + + private OnSeekbarChangeListener onSeekbarChangeListener; + private OnSeekbarFinalValueListener onSeekbarFinalValueListener; + + private float absoluteMinValue; + private float absoluteMaxValue; + private float minValue; + private float maxValue; + private float minStartValue; + private float steps; + + + private int mActivePointerId = INVALID_POINTER_ID; + + private int position; + private int nextPosition; + private int dataType; + private float cornerRadius; + private int barColorMode; + private int barColor; + private int barGradientStart; + private int barGradientEnd; + private int barHighlightColorMode; + private int barHighlightColor; + private int barHighlightGradientStart; + private int barHighlightGradientEnd; + private int thumbColor; + private int thumbColorNormal; + private int thumbColorPressed; + private boolean seekBarTouchEnabled; + private float barPadding; + private float _barHeight; + private float barHeight; + private float thumbWidth = 30f; + private float thumbHeight = 30f; + private float thumbDiameter; + private Drawable thumbDrawable; + private Drawable thumbDrawablePressed; + private Bitmap thumb; + private Bitmap thumbPressed; + private Thumb pressedThumb; + private double normalizedMinValue = 0d; + private double normalizedMaxValue = 100d; + private int pointerIndex; + + private RectF _rect; + private Paint _paint; + + private RectF rectThumb; + + private boolean mIsDragging; + + ////////////////////////////////////////// + // ENUMERATION + ////////////////////////////////////////// + + protected enum Thumb {MIN} + + ////////////////////////////////////////// + // CONSTRUCTOR + ////////////////////////////////////////// + + public CrystalSeekbar(Context context) { + this(context, null); + } + + public CrystalSeekbar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CrystalSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + // prevent render is in edit mode + if (isInEditMode()) return; + + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CrystalSeekbar); + try { + cornerRadius = getCornerRadius(array); + minValue = getMinValue(array); + maxValue = getMaxValue(array); + minStartValue = getMinStartValue(array); + steps = getSteps(array); + _barHeight = getBarHeight(array); + barColorMode = getBarColorMode(array); + barColor = getBarColor(array); + barGradientStart = getBarGradientStart(array); + barGradientEnd = getBarGradientEnd(array); + barHighlightColorMode = getBarHighlightColorMode(array); + barHighlightColor = getBarHighlightColor(array); + barHighlightGradientStart = getBarHighlightGradientStart(array); + barHighlightGradientEnd = getBarHighlightGradientEnd(array); + thumbColorNormal = getThumbColor(array); + thumbColorPressed = getThumbColorPressed(array); + thumbDrawable = getThumbDrawable(array); + thumbDrawablePressed = getThumbDrawablePressed(array); + dataType = getDataType(array); + position = getPosition(array); + nextPosition = position; + thumbDiameter = getDiameter(array); + seekBarTouchEnabled = isSeekBarTouchEnabled(array); + } finally { + array.recycle(); + } + + init(); + } + + ////////////////////////////////////////// + // INITIALIZING + ////////////////////////////////////////// + + protected void init() { + absoluteMinValue = minValue; + absoluteMaxValue = maxValue; + thumbColor = thumbColorNormal; + thumb = getBitmap(thumbDrawable); + thumbPressed = getBitmap(thumbDrawablePressed); + thumbPressed = (thumbPressed == null) ? thumb : thumbPressed; + + thumbWidth = getThumbWidth(); + thumbWidth = getThumbWidth(); + thumbHeight = getThumbHeight(); + + barHeight = getBarHeight(); + barPadding = getBarPadding(); + + _paint = new Paint(Paint.ANTI_ALIAS_FLAG); + _rect = new RectF(); + rectThumb = new RectF(); + + pressedThumb = null; + + setMinStartValue(); + + setWillNotDraw(false); + } + + ////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////// + + public CrystalSeekbar setCornerRadius(float cornerRadius) { + this.cornerRadius = cornerRadius; + return this; + } + + public CrystalSeekbar setMinValue(float minValue) { + this.minValue = minValue; + this.absoluteMinValue = minValue; + return this; + } + + public CrystalSeekbar setMaxValue(float maxValue) { + this.maxValue = maxValue; + this.absoluteMaxValue = maxValue; + return this; + } + + public CrystalSeekbar setMinStartValue(float minStartValue) { + this.minStartValue = minStartValue; + return this; + } + + public CrystalSeekbar setSteps(float steps) { + this.steps = steps; + return this; + } + + public CrystalSeekbar setBarHeight(float barHeight) { + this._barHeight = barHeight; + return this; + } + + public CrystalSeekbar setBarColorMode(int barColorMode) { + this.barColorMode = barColorMode; + return this; + } + + public CrystalSeekbar setBarColor(int barColor) { + this.barColor = barColor; + return this; + } + + public CrystalSeekbar setBarGradientStart(int barGradientStart) { + this.barGradientStart = barGradientStart; + return this; + } + + public CrystalSeekbar setBarGradientEnd(int barGradientEnd) { + this.barGradientEnd = barGradientEnd; + return this; + } + + public CrystalSeekbar setBarHighlightColorMode(int barHighlightColorMode) { + this.barHighlightColorMode = barHighlightColorMode; + return this; + } + + public CrystalSeekbar setBarHighlightColor(int barHighlightColor) { + this.barHighlightColor = barHighlightColor; + return this; + } + + public CrystalSeekbar setBarHighlightGradientStart(int barHighlightGradientStart) { + this.barHighlightGradientStart = barHighlightGradientStart; + return this; + } + + public CrystalSeekbar setBarHighlightGradientEnd(int barHighlightGradientEnd) { + this.barHighlightGradientEnd = barHighlightGradientEnd; + return this; + } + + public CrystalSeekbar setThumbColor(int leftThumbColorNormal) { + this.thumbColorNormal = leftThumbColorNormal; + return this; + } + + public CrystalSeekbar setThumbHighlightColor(int leftThumbColorPressed) { + this.thumbColorPressed = leftThumbColorPressed; + return this; + } + + public CrystalSeekbar setThumbDrawable(int resId) { + setThumbDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalSeekbar setThumbDrawable(Drawable drawable) { + setThumbBitmap(getBitmap(drawable)); + return this; + } + + public CrystalSeekbar setThumbBitmap(Bitmap bitmap) { + thumb = bitmap; + return this; + } + + public CrystalSeekbar setThumbHighlightDrawable(int resId) { + setThumbHighlightDrawable(ContextCompat.getDrawable(getContext(), resId)); + return this; + } + + public CrystalSeekbar setThumbHighlightDrawable(Drawable drawable) { + setThumbHighlightBitmap(getBitmap(drawable)); + return this; + } + + public CrystalSeekbar setThumbHighlightBitmap(Bitmap bitmap) { + thumbPressed = bitmap; + return this; + } + + /* add support to customize thumb size */ + + public CrystalSeekbar setThumbSize(float thumbSize) { + final float scale = getContext().getResources().getDisplayMetrics().density; + this.thumbHeight = thumbSize * scale + 0.5f; + this.thumbWidth = thumbSize * scale + 0.5f; + this.thumbDiameter = (thumbSize * scale + 0.5f) / 2; + return this; + } + + public CrystalSeekbar setDataType(int dataType) { + this.dataType = dataType; + return this; + } + + public CrystalSeekbar setPosition(int pos) { + this.nextPosition = pos; + return this; + } + + public void setOnSeekbarChangeListener(OnSeekbarChangeListener onSeekbarChangeListener) { + this.onSeekbarChangeListener = onSeekbarChangeListener; + if (this.onSeekbarChangeListener != null) { + this.onSeekbarChangeListener.valueChanged(getSelectedMinValue()); + } + } + + public void setOnSeekbarFinalValueListener(OnSeekbarFinalValueListener onSeekbarFinalValueListener) { + this.onSeekbarFinalValueListener = onSeekbarFinalValueListener; + } + + public Thumb getPressedThumb() { + return pressedThumb; + } + + public RectF getThumbRect() { + return rectThumb; + } + + public float getCornerRadius() { + return cornerRadius; + } + + public float getMinValue() { + return minValue; + } + + public float getMaxValue() { + return maxValue; + } + + public float getMinStartValue() { + return minStartValue; + } + + public float getSteps() { + return steps; + } + + public int getBarColor() { + return barColor; + } + + public int getBarHighlightColor() { + return barHighlightColor; + } + + public int getLeftThumbColor() { + return thumbColor; + } + + public int getLeftThumbColorPressed() { + return thumbColorPressed; + } + + public Drawable getLeftDrawable() { + return thumbDrawable; + } + + public Drawable getLeftDrawablePressed() { + return thumbDrawablePressed; + } + + public int getDataType() { + return dataType; + } + + public int getPosition() { + return this.position; + } + + public float getThumbWidth() { + return (thumb != null) ? thumb.getWidth() : thumbWidth; + } + + public float getThumbHeight() { + return (thumb != null) ? thumb.getHeight() : thumbHeight; + } + + protected float getThumbDiameter() { + return (thumbDiameter > 0) ? thumbDiameter : getResources().getDimension(R.dimen.thumb_width); + } + + public float getBarHeight() { + return _barHeight > 0 ? _barHeight : (thumbHeight * 0.5f) * 0.3f; + } + + public float getDiameter(final TypedArray typedArray) { + return typedArray.getDimensionPixelSize(R.styleable.CrystalSeekbar_thumb_diameter, getResources().getDimensionPixelSize(R.dimen.thumb_height)); + } + + protected boolean isSeekBarTouchEnabled(final TypedArray typedArray){ + return typedArray.getBoolean(R.styleable.CrystalSeekbar_seek_bar_touch_enabled, false); + } + + public float getBarPadding() { + return thumbWidth * 0.5f; + } + + public Number getSelectedMinValue() { + double nv = normalizedMinValue; + if (steps > 0 && steps <= ((absoluteMaxValue) / 2)) { + float stp = steps / (absoluteMaxValue - absoluteMinValue) * 100; + double half_step = stp / 2; + double mod = nv % stp; + if (mod > half_step) { + nv = nv - mod; + nv = nv + stp; + } else { + nv = nv - mod; + } + } else { + if (steps != NO_STEP) + throw new IllegalStateException("steps out of range " + steps); + } + + nv = (position == Position.LEFT) ? nv : Math.abs(nv - maxValue); + return formatValue(normalizedToValue(nv)); + } + + public Number getSelectedMaxValue() { + + double nv = normalizedMaxValue; + if (steps > 0 && steps <= (absoluteMaxValue / 2)) { + float stp = steps / (absoluteMaxValue - absoluteMinValue) * 100; + double half_step = stp / 2; + double mod = nv % stp; + if (mod > half_step) { + nv = nv - mod; + nv = nv + stp; + } else { + nv = nv - mod; + } + } else { + if (steps != NO_STEP) + throw new IllegalStateException("steps out of range " + steps); + } + + return formatValue(normalizedToValue(nv)); + } + + public void apply() { + + // reset normalize min and max value + //normalizedMinValue = 0d; + //normalizedMaxValue = 100d; + + thumbWidth = (thumb != null) ? thumb.getWidth() : getResources().getDimension(R.dimen.thumb_width); + thumbHeight = (thumb != null) ? thumb.getHeight() : getResources().getDimension(R.dimen.thumb_height); + + barHeight = (thumbHeight * 0.5f) * 0.3f; + barPadding = thumbWidth * 0.5f; + + // set min start value + if (minStartValue <= minValue) { + minStartValue = 0; + setNormalizedMinValue(minStartValue); + } else if (minStartValue > maxValue) { + minStartValue = maxValue; + setNormalizedMinValue(minStartValue); + } else { + //minStartValue = (long)getSelectedMinValue(); + if (nextPosition != position) { + minStartValue = (float) Math.abs(normalizedMaxValue - normalizedMinValue); + } + if (minStartValue > minValue) { + minStartValue = Math.min(minStartValue, absoluteMaxValue); + minStartValue -= absoluteMinValue; + minStartValue = minStartValue / (absoluteMaxValue - absoluteMinValue) * 100; + } + + setNormalizedMinValue(minStartValue); + position = nextPosition; + + } + + invalidate(); + if (onSeekbarChangeListener != null) { + onSeekbarChangeListener.valueChanged(getSelectedMinValue()); + } + } + + ////////////////////////////////////////// + // PROTECTED METHODS + ////////////////////////////////////////// + + protected Bitmap getBitmap(Drawable drawable) { + return (drawable != null) ? ((BitmapDrawable) drawable).getBitmap() : null; + } + + protected float getCornerRadius(final TypedArray typedArray) { + return typedArray.getFloat(R.styleable.CrystalSeekbar_corner_radius, 0f); + } + + protected float getMinValue(final TypedArray typedArray) { + return typedArray.getFloat(R.styleable.CrystalSeekbar_min_value, 0f); + } + + protected float getMaxValue(final TypedArray typedArray) { + return typedArray.getFloat(R.styleable.CrystalSeekbar_max_value, 100f); + } + + protected float getMinStartValue(final TypedArray typedArray) { + return typedArray.getFloat(R.styleable.CrystalSeekbar_min_start_value, minValue); + } + + protected float getSteps(final TypedArray typedArray) { + return typedArray.getFloat(R.styleable.CrystalSeekbar_steps, NO_STEP); + } + + protected float getBarHeight(final TypedArray typedArray){ + return typedArray.getDimensionPixelSize(R.styleable.CrystalSeekbar_bar_height, 0); + } + + protected int getBarColorMode(final TypedArray typedArray) { + return typedArray.getInt(R.styleable.CrystalSeekbar_bar_color_mode, ColorMode.SOLID); + } + + protected int getBarColor(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_color, Color.GRAY); + } + + protected int getBarGradientStart(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_gradient_start, Color.GRAY); + } + + protected int getBarGradientEnd(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_gradient_end, Color.DKGRAY); + } + + protected int getBarHighlightColorMode(final TypedArray typedArray) { + return typedArray.getInt(R.styleable.CrystalSeekbar_bar_highlight_color_mode, ColorMode.SOLID); + } + + protected int getBarHighlightColor(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_highlight_color, Color.BLACK); + } + + protected int getBarHighlightGradientStart(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_highlight_gradient_start, Color.DKGRAY); + } + + protected int getBarHighlightGradientEnd(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_bar_highlight_gradient_end, Color.BLACK); + } + + protected int getThumbColor(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_thumb_color, Color.BLACK); + } + + protected int getThumbColorPressed(final TypedArray typedArray) { + return typedArray.getColor(R.styleable.CrystalSeekbar_thumb_color_pressed, Color.DKGRAY); + } + + protected Drawable getThumbDrawable(final TypedArray typedArray) { + return typedArray.getDrawable(R.styleable.CrystalSeekbar_thumb_image); + } + + protected Drawable getThumbDrawablePressed(final TypedArray typedArray) { + return typedArray.getDrawable(R.styleable.CrystalSeekbar_thumb_image_pressed); + } + + protected int getDataType(final TypedArray typedArray) { + return typedArray.getInt(R.styleable.CrystalSeekbar_data_type, DataType.INTEGER); + } + + protected final int getPosition(final TypedArray typedArray) { + final int pos = typedArray.getInt(R.styleable.CrystalSeekbar_position, Position.LEFT); + + normalizedMinValue = (pos == Position.LEFT) ? normalizedMinValue : normalizedMaxValue; + return pos; + } + + protected void setupBar(final Canvas canvas, final Paint paint, final RectF rect) { + rect.left = barPadding; + rect.top = 0.5f * (getHeight() - barHeight); + rect.right = getWidth() - barPadding; + rect.bottom = 0.5f * (getHeight() + barHeight); + + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + + if (barColorMode == ColorMode.SOLID) { + paint.setColor(barColor); + drawBar(canvas, paint, rect); + + } else { + paint.setShader( + new LinearGradient(rect.left, rect.bottom, rect.right, rect.top, + barGradientStart, + barGradientEnd, + Shader.TileMode.MIRROR) + ); + + drawBar(canvas, paint, rect); + + paint.setShader(null); + } + } + + protected void drawBar(final Canvas canvas, final Paint paint, final RectF rect) { + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint); + } + + protected void setupHighlightBar(final Canvas canvas, final Paint paint, final RectF rect) { + if (position == Position.RIGHT) { + rect.left = normalizedToScreen(normalizedMinValue) + (getThumbWidth() / 2); + rect.right = getWidth() - (getThumbWidth() / 2); + } else { + rect.left = getThumbWidth() / 2; + rect.right = normalizedToScreen(normalizedMinValue) + (getThumbWidth() / 2); + } + + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + + if (barHighlightColorMode == ColorMode.SOLID) { + paint.setColor(barHighlightColor); + drawHighlightBar(canvas, paint, rect); + + } else { + paint.setShader( + new LinearGradient(rect.left, rect.bottom, rect.right, rect.top, + barHighlightGradientStart, + barHighlightGradientEnd, + Shader.TileMode.MIRROR) + ); + + drawHighlightBar(canvas, paint, rect); + + paint.setShader(null); + } + } + + protected void drawHighlightBar(final Canvas canvas, final Paint paint, final RectF rect) { + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint); + } + + protected void setupLeftThumb(final Canvas canvas, final Paint paint, final RectF rect) { + + thumbColor = (Thumb.MIN.equals(pressedThumb)) ? thumbColorPressed : thumbColorNormal; + paint.setColor(thumbColor); + + rectThumb.left = normalizedToScreen(normalizedMinValue); + rectThumb.right = Math.min(rectThumb.left + (getThumbWidth() / 2) + barPadding, getWidth()); + + rectThumb.top = 0f; + rectThumb.bottom = thumbHeight; + + if (thumb != null) { + Bitmap lThumb = (Thumb.MIN.equals(pressedThumb)) ? thumbPressed : thumb; + drawLeftThumbWithImage(canvas, paint, rectThumb, lThumb); + } else { + drawLeftThumbWithColor(canvas, paint, rectThumb); + } + } + + protected void drawLeftThumbWithColor(final Canvas canvas, final Paint paint, final RectF rect) { + canvas.drawOval(rect, paint); + } + + protected void drawLeftThumbWithImage(final Canvas canvas, final Paint paint, final RectF rect, final Bitmap image) { + canvas.drawBitmap(image, rect.left, rect.top, paint); + } + + protected void trackTouchEvent(MotionEvent event) { + final int pointerIndex = event.findPointerIndex(mActivePointerId); + try { + final float x = event.getX(pointerIndex); + + if (Thumb.MIN.equals(pressedThumb)) { + setNormalizedMinValue(screenToNormalized(x)); + } + } catch (Exception ignored) { + } + } + + protected void touchDown(final float x, final float y) { + + } + + protected void touchMove(final float x, final float y) { + + } + + protected void touchUp(final float x, final float y) { + + } + + protected int getMeasureSpecWith(int widthMeasureSpec) { + int width = 200; + if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) { + width = MeasureSpec.getSize(widthMeasureSpec); + } + return width; + } + + protected int getMeasureSpecHeight(int heightMeasureSpec) { + int height = Math.round(thumbHeight); + if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) { + height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); + } + return height; + } + + protected final void log(Object object) { + Log.d("CRS=>", String.valueOf(object)); + } + + ////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////// + + private void setMinStartValue() { + if (minStartValue > minValue && minStartValue < maxValue) { + minStartValue = Math.min(minStartValue, absoluteMaxValue); + minStartValue -= absoluteMinValue; + minStartValue = minStartValue / (absoluteMaxValue - absoluteMinValue) * 100; + setNormalizedMinValue(minStartValue); + } + } + + private Thumb evalPressedThumb(float touchX) { + Thumb result = null; + + boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue); + if (seekBarTouchEnabled || minThumbPressed) { + // if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore. + result = Thumb.MIN; + } + return result; + } + + private boolean isInThumbRange(float touchX, double normalizedThumbValue) { + float thumbPos = normalizedToScreen(normalizedThumbValue); + float left = thumbPos - (getThumbWidth() / 2); + float right = thumbPos + (getThumbWidth() / 2); + float x = touchX - (getThumbWidth() / 2); + if (thumbPos > (getWidth() - thumbWidth)) x = touchX; + return (x >= left && x <= right); + } + + private void onStartTrackingTouch() { + mIsDragging = true; + } + + private void onStopTrackingTouch() { + mIsDragging = false; + } + + private float normalizedToScreen(double normalizedCoord) { + + float width = getWidth() - (barPadding * 2); + return (float) normalizedCoord / 100f * width; + } + + private double screenToNormalized(float screenCoord) { + double width = getWidth(); + + if (width <= 2 * barPadding) { + // prevent division by zero, simply return 0. + return 0d; + } else { + width = width - (barPadding * 2); + double result = screenCoord / width * 100d; + result = result - (barPadding / width * 100d); + result = Math.min(100d, Math.max(0d, result)); + return result; + + } + } + + private void setNormalizedMinValue(double value) { + normalizedMinValue = Math.max(0d, Math.min(100d, Math.min(value, normalizedMaxValue))); + invalidate(); + } + + private void setNormalizedMaxValue(double value) { + normalizedMaxValue = Math.max(0d, Math.min(100d, Math.max(value, normalizedMinValue))); + invalidate(); + } + + private double normalizedToValue(double normalized) { + double val = normalized / 100 * (maxValue - minValue); + val = (position == Position.LEFT) ? val + minValue : val; + return val; + } + + private void attemptClaimDrag() { + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + } + + private Number formatValue(T value) throws IllegalArgumentException { + final Double v = (Double) value; + if (dataType == DataType.LONG) { + return v.longValue(); + } + if (dataType == DataType.DOUBLE) { + return v; + } + if (dataType == DataType.INTEGER) { + return Math.round(v); + } + if (dataType == DataType.FLOAT) { + return v.floatValue(); + } + if (dataType == DataType.SHORT) { + return v.shortValue(); + } + if (dataType == DataType.BYTE) { + return v.byteValue(); + } + throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported"); + } + + ////////////////////////////////////////// + // OVERRIDE METHODS + ////////////////////////////////////////// + + @Override + protected synchronized void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // prevent render is in edit mode + if (isInEditMode()) return; + + // setup bar + setupBar(canvas, _paint, _rect); + + // setup seek bar active range line + setupHighlightBar(canvas, _paint, _rect); + + // draw left thumb + setupLeftThumb(canvas, _paint, _rect); + + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getMeasureSpecWith(widthMeasureSpec), getMeasureSpecHeight(heightMeasureSpec)); + } + + /** + * Handles thumb selection and movement. Notifies listener callback on certain events. + */ + @Override + public synchronized boolean onTouchEvent(MotionEvent event) { + + if (!isEnabled()) + return false; + + + final int action = event.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + + case MotionEvent.ACTION_DOWN: + mActivePointerId = event.getPointerId(event.getPointerCount() - 1); + pointerIndex = event.findPointerIndex(mActivePointerId); + float mDownMotionX = event.getX(pointerIndex); + + pressedThumb = evalPressedThumb(mDownMotionX); + + if (pressedThumb == null) return super.onTouchEvent(event); + + touchDown(event.getX(pointerIndex), event.getY(pointerIndex)); + setPressed(true); + invalidate(); + onStartTrackingTouch(); + trackTouchEvent(event); + attemptClaimDrag(); + + break; + + case MotionEvent.ACTION_MOVE: + if (pressedThumb != null) { + + if (mIsDragging) { + touchMove(event.getX(pointerIndex), event.getY(pointerIndex)); + trackTouchEvent(event); + } + + if (onSeekbarChangeListener != null) { + onSeekbarChangeListener.valueChanged(getSelectedMinValue()); + } + } + break; + case MotionEvent.ACTION_UP: + if (mIsDragging) { + trackTouchEvent(event); + onStopTrackingTouch(); + setPressed(false); + touchUp(event.getX(pointerIndex), event.getY(pointerIndex)); + if (onSeekbarFinalValueListener != null) { + onSeekbarFinalValueListener.finalValue(getSelectedMinValue()); + } + } else { + // Touch up when we never crossed the touch slop threshold + // should be interpreted as a tap-seek to that location. + onStartTrackingTouch(); + trackTouchEvent(event); + onStopTrackingTouch(); + } + + pressedThumb = null; + invalidate(); + if (onSeekbarChangeListener != null) { + onSeekbarChangeListener.valueChanged(getSelectedMinValue()); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: { + //final int index = event.getPointerCount() - 1; + // final int index = ev.getActionIndex(); + /*mDownMotionX = event.getX(index); + mActivePointerId = event.getPointerId(index); + invalidate();*/ + break; + } + case MotionEvent.ACTION_POINTER_UP: + /*onSecondaryPointerUp(event);*/ + invalidate(); + break; + case MotionEvent.ACTION_CANCEL: + if (mIsDragging) { + onStopTrackingTouch(); + setPressed(false); + touchUp(event.getX(pointerIndex), event.getY(pointerIndex)); + } + invalidate(); // see above explanation + break; + } + + return true; + + } +} diff --git a/android/crystalrangeseekbar/src/main/res/values/attr.xml b/android/crystalrangeseekbar/src/main/res/values/attr.xml new file mode 100644 index 0000000..302e7c5 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/res/values/attr.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/crystalrangeseekbar/src/main/res/values/dimens.xml b/android/crystalrangeseekbar/src/main/res/values/dimens.xml new file mode 100644 index 0000000..3d9eaa5 --- /dev/null +++ b/android/crystalrangeseekbar/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + 15dp + 15dp + + 70dp + 70dp + diff --git a/android/crystalrangeseekbar/src/test/java/com/example/crystalrangeseekbar/ExampleUnitTest.java b/android/crystalrangeseekbar/src/test/java/com/example/crystalrangeseekbar/ExampleUnitTest.java new file mode 100644 index 0000000..a173384 --- /dev/null +++ b/android/crystalrangeseekbar/src/test/java/com/example/crystalrangeseekbar/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package com.example.crystalrangeseekbar; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/android/jesster2k10reactnativerangeslider.iml b/android/jesster2k10reactnativerangeslider.iml index b5da8a7..5600d1b 100644 --- a/android/jesster2k10reactnativerangeslider.iml +++ b/android/jesster2k10reactnativerangeslider.iml @@ -32,7 +32,7 @@