From 01dc4982f6912d35bb878d76f8bb3732382b1594 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Tue, 10 May 2022 12:48:25 -0400 Subject: [PATCH 01/17] Adds Kotlin to project Updates Gradle Updates project to Java 11 Updates Support Library --- app/build.gradle | 6 ++++-- build.gradle | 17 +++++++++-------- circularSeekBar/build.gradle | 15 ++++++++++++++- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 32c58203..51ffdbb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' // Define some common closures (methods) for code share ext.getTagCount = { @@ -18,7 +19,7 @@ android { defaultConfig { applicationId "me.tankery.app.circularseekbar" minSdkVersion 15 - targetSdkVersion 28 + targetSdkVersion 31 versionCode getTagCount() versionName getLatestTaggedVersion() } @@ -33,5 +34,6 @@ android { dependencies { implementation project(':circularSeekBar') - implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" + implementation "com.android.support:support-annotations:$supportLibVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" } diff --git a/build.gradle b/build.gradle index 20bc7f14..49479190 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,20 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + kotlinVersion = '1.6.21' + compileSdkVersion = 31 + buildToolsVersion = "31.0.0" + supportLibVersion = "28.0.0" + } + repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:7.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -19,10 +27,3 @@ allprojects { mavenCentral() } } - -ext { - compileSdkVersion = 27 - buildToolsVersion = "27.0.3" - - supportLibVersion = "27.1.1" -} diff --git a/circularSeekBar/build.gradle b/circularSeekBar/build.gradle index fa604a9b..1d67a889 100644 --- a/circularSeekBar/build.gradle +++ b/circularSeekBar/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' apply plugin: 'maven-publish' android { @@ -7,7 +8,7 @@ android { defaultConfig { minSdkVersion 10 - targetSdkVersion 28 + targetSdkVersion 31 versionName version } buildTypes { @@ -16,9 +17,21 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"] + } } dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation "com.android.support:support-annotations:$supportLibVersion" } apply from: rootProject.file('gradle/mvn-push.gradle') diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2dcfd3c2..61795ed9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip From 73c2206416e91a259d1c67ca97b96d77d7f5c047 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Tue, 10 May 2022 12:49:46 -0400 Subject: [PATCH 02/17] Updates AndroidManifest.xml with required exported attribute Removes redundant label --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 017560fd..5eec42e4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ android:theme="@style/AppTheme" > + android:exported="true" > From e64c58cbf9ae4a545e510d059f0b9e371cfe3ab6 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Tue, 10 May 2022 12:50:35 -0400 Subject: [PATCH 03/17] Converts MainActivity to Kotlin using stale branch's changes --- .../app/circularseekbar/MainActivity.java | 50 ------------------- .../app/circularseekbar/MainActivity.kt | 41 +++++++++++++++ 2 files changed, 41 insertions(+), 50 deletions(-) delete mode 100644 app/src/main/java/me/tankery/app/circularseekbar/MainActivity.java create mode 100644 app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt diff --git a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.java b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.java deleted file mode 100644 index d21acab5..00000000 --- a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package me.tankery.app.circularseekbar; - -import android.app.Activity; -import android.os.Bundle; -import android.support.annotation.IdRes; -import android.util.Log; -import android.widget.TextView; - -import me.tankery.lib.circularseekbar.CircularSeekBar; - - -public class MainActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - final TextView textEvent = findTheViewById(R.id.text_event); - final TextView textProgress = findTheViewById(R.id.text_progress); - - CircularSeekBar seekBar = (CircularSeekBar) findViewById(R.id.seek_bar); - seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() { - @Override - public void onProgressChanged(CircularSeekBar circularSeekBar, float progress, boolean fromUser) { - String message = String.format("Progress changed to %.2f, fromUser %s", progress, fromUser); - Log.d("Main", message); - textProgress.setText(message); - } - - @Override - public void onStopTrackingTouch(CircularSeekBar seekBar) { - Log.d("Main", "onStopTrackingTouch"); - textEvent.setText(""); - } - - @Override - public void onStartTrackingTouch(CircularSeekBar seekBar) { - Log.d("Main", "onStartTrackingTouch"); - textEvent.setText("touched | "); - } - }); - } - - @SuppressWarnings("unchecked") - private T findTheViewById(@IdRes int id) { - return (T) super.findViewById(id); - } - -} diff --git a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt new file mode 100644 index 00000000..2f815487 --- /dev/null +++ b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt @@ -0,0 +1,41 @@ +package me.tankery.app.circularseekbar + +import android.app.Activity +import android.os.Bundle +import android.util.Log +import android.widget.TextView +import me.tankery.app.circularseekbar.R.id +import me.tankery.app.circularseekbar.R.layout +import me.tankery.lib.circularseekbar.CircularSeekBar +import me.tankery.lib.circularseekbar.CircularSeekBar.OnCircularSeekBarChangeListener + +class MainActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(layout.activity_main) + + val textEvent = findViewById(id.text_event) + val textProgress = findViewById(id.text_progress) + val seekBar = findViewById(id.seek_bar) + + seekBar.setOnSeekBarChangeListener(object : OnCircularSeekBarChangeListener { + override fun onProgressChanged(circularSeekBar: CircularSeekBar?, progress: Float, fromUser: Boolean) { + val message = String.format("Progress changed to %.2f, fromUser %s", progress, fromUser) + Log.d("Main", message) + textProgress.text = message + } + + override fun onStopTrackingTouch(seekBar: CircularSeekBar?) { + Log.d("Main", "onStopTrackingTouch") + textEvent.text = "" + } + + override fun onStartTrackingTouch(seekBar: CircularSeekBar?) { + Log.d("Main", "onStartTrackingTouch") + textEvent.text = "touched | " + } + }) + } +} \ No newline at end of file From 198e91994232163ea88e96804aae40b292278157 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 11 May 2022 14:18:32 -0400 Subject: [PATCH 04/17] Converts CircularSeekBar to Kotlin as CircularSeekBarV2.kt --- .../app/circularseekbar/MainActivity.kt | 16 +- app/src/main/res/layout/activity_main.xml | 10 +- .../lib/circularseekbar/CircularSeekBarV2.kt | 1206 +++++++++++++++++ circularSeekBar/src/main/res/values/attrs.xml | 1 - 4 files changed, 1220 insertions(+), 13 deletions(-) create mode 100644 circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBarV2.kt diff --git a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt index 2f815487..73269ad7 100644 --- a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt +++ b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt @@ -1,13 +1,13 @@ package me.tankery.app.circularseekbar +import android.annotation.SuppressLint import android.app.Activity import android.os.Bundle import android.util.Log import android.widget.TextView import me.tankery.app.circularseekbar.R.id import me.tankery.app.circularseekbar.R.layout -import me.tankery.lib.circularseekbar.CircularSeekBar -import me.tankery.lib.circularseekbar.CircularSeekBar.OnCircularSeekBarChangeListener +import me.tankery.lib.circularseekbar.CircularSeekBarV2 class MainActivity : Activity() { @@ -18,21 +18,23 @@ class MainActivity : Activity() { val textEvent = findViewById(id.text_event) val textProgress = findViewById(id.text_progress) - val seekBar = findViewById(id.seek_bar) + val seekBar = findViewById(id.seek_bar) - seekBar.setOnSeekBarChangeListener(object : OnCircularSeekBarChangeListener { - override fun onProgressChanged(circularSeekBar: CircularSeekBar?, progress: Float, fromUser: Boolean) { + seekBar.setOnSeekBarChangeListener(object : + CircularSeekBarV2.OnCircularSeekBarChangeListener { + override fun onProgressChanged(circularSeekBar: CircularSeekBarV2?, progress: Float, fromUser: Boolean) { val message = String.format("Progress changed to %.2f, fromUser %s", progress, fromUser) Log.d("Main", message) textProgress.text = message } - override fun onStopTrackingTouch(seekBar: CircularSeekBar?) { + override fun onStopTrackingTouch(seekBar: CircularSeekBarV2?) { Log.d("Main", "onStopTrackingTouch") textEvent.text = "" } - override fun onStartTrackingTouch(seekBar: CircularSeekBar?) { + @SuppressLint("SetTextI18n") + override fun onStartTrackingTouch(seekBar: CircularSeekBarV2?) { Log.d("Main", "onStartTrackingTouch") textEvent.text = "touched | " } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0ed82c8b..e142fd0d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,7 +15,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - - 0) { + if (max <= mProgress) { + mProgress = 0f // If the new max is less than current progress, set progress to zero + mOnCircularSeekBarChangeListener?.onProgressChanged( + this, + if (mIsInNegativeHalf) -mProgress else mProgress, + false + ) + } + field = max + recalculateAll() + invalidate() + } + } + + /** + * Progress value that this CircularSeekBar is representing. + */ + private var mProgress = 0f + + /** + * Used for enabling/disabling the negative progress bar. + */ + var isNegativeEnabled = false + + /** + * If true, then the user can specify the X and Y radii. + * If false, then the View itself determines the size of the CircularSeekBar. + */ + private var mCustomRadii = false + + /** + * Maintain a perfect circle (equal x and y radius), regardless of view or custom attributes. + * The smaller of the two radii will always be used in this case. + * The default is to be a circle and not an ellipse, due to the behavior of the ellipse. + */ + private var mMaintainEqualCircle = false + + /** + * Once a user has touched the circle, this determines if moving outside the circle is able + * to change the position of the pointer (and in turn, the progress). + */ + private var mMoveOutsideCircle = false + + /** + * Used for enabling/disabling the lock option for easier hitting of the 0 progress mark. + */ + var isLockEnabled = true + + /** + * Used for when the user moves beyond the start of the circle when moving counter clockwise. + * Makes it easier to hit the 0 progress mark. + */ + private var mLockAtStart = true + + /** + * Used for when the user moves beyond the end of the circle when moving clockwise. + * Makes it easier to hit the 100% (max) progress mark. + */ + private var mLockAtEnd = false + + /** + * If progress is zero, hide the progress bar. + */ + private var mHideProgressWhenEmpty = false + + /** + * When the user is touching the circle on ACTION_DOWN, this is set to true. + * Used when touching the CircularSeekBar. + */ + private var mUserIsMovingPointer = false + + /** + * The width of the circle used in the `RectF` that is used to draw it. + * Based on either the View width or the custom X radius. + */ + private var mCircleWidth = 0f + + /** + * The height of the circle used in the `RectF` that is used to draw it. + * Based on either the View width or the custom Y radius. + */ + private var mCircleHeight = 0f + + /** + * Represents the progress mark on the circle, in geometric degrees. + * This is not provided by the user; it is calculated; + */ + private var mPointerPosition = 0f + + /** + * Pointer position in terms of X and Y coordinates. + */ + private val mPointerPositionXY = FloatArray(2) + + /** + * Listener. + */ + private var mOnCircularSeekBarChangeListener: OnCircularSeekBarChangeListener? = null + + init { + val attrArray = context.theme.obtainStyledAttributes(attrs, R.styleable.cs_CircularSeekBar, defStyleAttr, defStyleRes) + initPaints() + initAttributes(attrArray) + attrArray.recycle() + } + + /** + * Initialize the CircularSeekBar with the attributes from the XML style. + * Uses the defaults defined at the top of this file when an attribute is not specified by the user. + * @param attrArray TypedArray containing the attributes. + */ + private fun initAttributes(attrArray: TypedArray) { + mCircleXRadius = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_circle_x_radius, + DEFAULT_CIRCLE_X_RADIUS + ) + mCircleYRadius = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_circle_y_radius, + DEFAULT_CIRCLE_Y_RADIUS + ) + mPointerStrokeWidth = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_pointer_stroke_width, + DEFAULT_POINTER_STROKE_WIDTH + ) + mPointerHaloWidth = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_pointer_halo_width, + DEFAULT_POINTER_HALO_WIDTH + ) + mPointerHaloBorderWidth = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_pointer_halo_border_width, + DEFAULT_POINTER_HALO_BORDER_WIDTH + ) + mCircleStrokeWidth = attrArray.getDimension( + R.styleable.cs_CircularSeekBar_cs_circle_stroke_width, + DEFAULT_CIRCLE_STROKE_WIDTH + ) + val circleStyle = + attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_circle_style, DEFAULT_CIRCLE_STYLE) + mCircleStyle = Cap.values()[circleStyle] + mPointerColor = attrArray.getColor( + R.styleable.cs_CircularSeekBar_cs_pointer_color, + DEFAULT_POINTER_COLOR + ) + mPointerHaloColor = attrArray.getColor( + R.styleable.cs_CircularSeekBar_cs_pointer_halo_color, + DEFAULT_POINTER_HALO_COLOR + ) + mPointerHaloColorOnTouch = attrArray.getColor( + R.styleable.cs_CircularSeekBar_cs_pointer_halo_color_ontouch, + DEFAULT_POINTER_HALO_COLOR_ONTOUCH + ) + mCircleColor = + attrArray.getColor(R.styleable.cs_CircularSeekBar_cs_circle_color, DEFAULT_CIRCLE_COLOR) + mCircleProgressColor = attrArray.getColor( + R.styleable.cs_CircularSeekBar_cs_circle_progress_color, + DEFAULT_CIRCLE_PROGRESS_COLOR + ) + mCircleFillColor = attrArray.getColor( + R.styleable.cs_CircularSeekBar_cs_circle_fill, + DEFAULT_CIRCLE_FILL_COLOR + ) + mPointerAlpha = Color.alpha(mPointerHaloColor) + mPointerAlphaOnTouch = attrArray.getInt( + R.styleable.cs_CircularSeekBar_cs_pointer_alpha_ontouch, + DEFAULT_POINTER_ALPHA_ONTOUCH + ) + if (mPointerAlphaOnTouch > 255 || mPointerAlphaOnTouch < 0) { + mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH + } + mMax = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_max, DEFAULT_MAX).toFloat() + mProgress = + attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_progress, DEFAULT_PROGRESS).toFloat() + mCustomRadii = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_use_custom_radii, + DEFAULT_USE_CUSTOM_RADII + ) + mMaintainEqualCircle = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_maintain_equal_circle, + DEFAULT_MAINTAIN_EQUAL_CIRCLE + ) + mMoveOutsideCircle = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_move_outside_circle, + DEFAULT_MOVE_OUTSIDE_CIRCLE + ) + isLockEnabled = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_lock_enabled, + DEFAULT_LOCK_ENABLED + ) + mDisablePointer = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_disable_pointer, + DEFAULT_DISABLE_POINTER + ) + isNegativeEnabled = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_negative_enabled, + DEFAULT_NEGATIVE_ENABLED + ) + mIsInNegativeHalf = false + mDisableProgressGlow = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_disable_progress_glow, + DEFAULT_DISABLE_PROGRESS_GLOW + ) + mHideProgressWhenEmpty = attrArray.getBoolean( + R.styleable.cs_CircularSeekBar_cs_hide_progress_when_empty, + DEFAULT_CS_HIDE_PROGRESS_WHEN_EMPTY + ) + + // Modulo 360 right now to avoid constant conversion + mStartAngle = (360f + attrArray.getFloat( + R.styleable.cs_CircularSeekBar_cs_start_angle, + DEFAULT_START_ANGLE + ) % 360f) % 360f + mEndAngle = (360f + attrArray.getFloat( + R.styleable.cs_CircularSeekBar_cs_end_angle, + DEFAULT_END_ANGLE + ) % 360f) % 360f + + // Disable negative progress if is semi-oval. + if (mStartAngle != mEndAngle) { + isNegativeEnabled = false + } + if (mStartAngle % 360f == mEndAngle % 360f) { + //mStartAngle = mStartAngle + 1f; + mEndAngle = mEndAngle - SMALL_DEGREE_BIAS + } + + // Modulo 360 right now to avoid constant conversion + mPointerAngle = (360f + attrArray.getFloat( + R.styleable.cs_CircularSeekBar_cs_pointer_angle, + DEFAULT_POINTER_ANGLE + ) % 360f) % 360f + if (mPointerAngle == 0f) { + mPointerAngle = SMALL_DEGREE_BIAS + } + if (mDisablePointer) { + mPointerStrokeWidth = 0f + mPointerHaloWidth = 0f + mPointerHaloBorderWidth = 0f + } + } + + /** + * Initializes the `Paint` objects with the appropriate styles. + */ + private fun initPaints() { + mCirclePaint.isAntiAlias = true + mCirclePaint.isDither = true + mCirclePaint.color = mCircleColor + mCirclePaint.strokeWidth = mCircleStrokeWidth + mCirclePaint.style = Paint.Style.STROKE + mCirclePaint.strokeJoin = Paint.Join.ROUND + mCirclePaint.strokeCap = mCircleStyle + + mCircleFillPaint.isAntiAlias = true + mCircleFillPaint.isDither = true + mCircleFillPaint.color = mCircleFillColor + mCircleFillPaint.style = Paint.Style.FILL + + mCircleProgressPaint.isAntiAlias = true + mCircleProgressPaint.isDither = true + mCircleProgressPaint.color = mCircleProgressColor + mCircleProgressPaint.strokeWidth = mCircleStrokeWidth + mCircleProgressPaint.style = Paint.Style.STROKE + mCircleProgressPaint.strokeJoin = Paint.Join.ROUND + mCircleProgressPaint.strokeCap = mCircleStyle + if (!mDisableProgressGlow) { + mCircleProgressGlowPaint.set(mCircleProgressPaint) + mCircleProgressGlowPaint.maskFilter = BlurMaskFilter( + PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE, + BlurMaskFilter.Blur.NORMAL + ) + } + + mPointerPaint.isAntiAlias = true + mPointerPaint.isDither = true + mPointerPaint.color = mPointerColor + mPointerPaint.strokeWidth = mPointerStrokeWidth + mPointerPaint.style = Paint.Style.STROKE + mPointerPaint.strokeJoin = Paint.Join.ROUND + mPointerPaint.strokeCap = mCircleStyle + + mPointerHaloPaint.set(mPointerPaint) + mPointerHaloPaint.color = mPointerHaloColor + mPointerHaloPaint.alpha = mPointerAlpha + mPointerHaloPaint.strokeWidth = mPointerStrokeWidth + mPointerHaloWidth * 2f + + mPointerHaloBorderPaint.set(mPointerPaint) + mPointerHaloBorderPaint.strokeWidth = mPointerHaloBorderWidth + mPointerHaloBorderPaint.style = Paint.Style.STROKE + } + + /** + * Calculates the total degrees between mStartAngle and mEndAngle, and sets mTotalCircleDegrees + * to this value. + */ + private fun calculateTotalDegrees() { + mTotalCircleDegrees = + (360f - (mStartAngle - mEndAngle)) % 360f // Length of the entire circle/arc + if (mTotalCircleDegrees <= 0f) { + mTotalCircleDegrees = 360f + } + } + + /** + * Calculate the degrees that the progress represents. Also called the sweep angle. + * Sets mProgressDegrees to that value. + */ + private fun calculateProgressDegrees() { + mProgressDegrees = + if (mIsInNegativeHalf) mStartAngle - mPointerPosition else mPointerPosition - mStartAngle // Verified + mProgressDegrees = + if (mProgressDegrees < 0) 360f + mProgressDegrees else mProgressDegrees // Verified + } + + /** + * Calculate the pointer position (and the end of the progress arc) in degrees. + * Sets mPointerPosition to that value. + */ + private fun calculatePointerPosition() { + val progressPercent = mProgress / mMax + val progressDegree = progressPercent * mTotalCircleDegrees + mPointerPosition = mStartAngle + if (mIsInNegativeHalf) -progressDegree else progressDegree + mPointerPosition = + (if (mPointerPosition < 0) 360f + mPointerPosition else mPointerPosition) % 360f + } + + private fun calculatePointerXYPosition() { + var pm = PathMeasure(mCircleProgressPath, false) + val returnValue = pm.getPosTan(pm.length, mPointerPositionXY, null) + if (!returnValue) { + pm = PathMeasure(mCirclePath, false) + pm.getPosTan(0f, mPointerPositionXY, null) + } + } + + /** + * Reset the `Path` objects with the appropriate values. + */ + private fun resetPaths() { + if (mIsInNegativeHalf) { + mCirclePath.reset() + mCirclePath.addArc(pathCircle, mStartAngle - mTotalCircleDegrees, mTotalCircleDegrees) + + // beside progress path it self, we also draw a extend arc to math the pointer arc. + val extendStart = mStartAngle - mProgressDegrees - mPointerAngle / 2.0f + var extendDegrees = mProgressDegrees + mPointerAngle + if (extendDegrees >= 360f) { + extendDegrees = 360f - SMALL_DEGREE_BIAS + } + mCircleProgressPath.reset() + mCircleProgressPath.addArc(pathCircle, extendStart, extendDegrees) + val pointerStart = mPointerPosition - mPointerAngle / 2.0f + mCirclePointerPath.reset() + mCirclePointerPath.addArc(pathCircle, pointerStart, mPointerAngle) + } else { + mCirclePath.reset() + mCirclePath.addArc(pathCircle, mStartAngle, mTotalCircleDegrees) + + // beside progress path it self, we also draw a extend arc to math the pointer arc. + val extendStart = mStartAngle - mPointerAngle / 2.0f + var extendDegrees = mProgressDegrees + mPointerAngle + if (extendDegrees >= 360f) { + extendDegrees = 360f - SMALL_DEGREE_BIAS + } + mCircleProgressPath.reset() + mCircleProgressPath.addArc(pathCircle, extendStart, extendDegrees) + val pointerStart = mPointerPosition - mPointerAngle / 2.0f + mCirclePointerPath.reset() + mCirclePointerPath.addArc(pathCircle, pointerStart, mPointerAngle) + } + } + + /** + * Initialize the `RectF` objects with the appropriate values. + */ + private fun resetRects() { + pathCircle[-mCircleWidth, -mCircleHeight, mCircleWidth] = mCircleHeight + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.translate(width / 2f, height / 2f) + canvas.drawPath(mCirclePath, mCircleFillPaint) + canvas.drawPath(mCirclePath, mCirclePaint) + val ableToGoNegative = + isNegativeEnabled && Math.abs(mTotalCircleDegrees - 360f) < SMALL_DEGREE_BIAS * 2 + // Hide progress bar when progress is 0 + // Also make sure we still draw progress when has pointer or able to go negative + val shouldHideProgress = mHideProgressWhenEmpty && mProgressDegrees == 0f && + mDisablePointer && !ableToGoNegative + if (!shouldHideProgress) { + if (!mDisableProgressGlow) { + canvas.drawPath(mCircleProgressPath, mCircleProgressGlowPaint) + } + canvas.drawPath(mCircleProgressPath, mCircleProgressPaint) + } + if (!mDisablePointer) { + if (mUserIsMovingPointer) { + canvas.drawPath(mCirclePointerPath, mPointerHaloPaint) + } + canvas.drawPath(mCirclePointerPath, mPointerPaint) + // TODO, find a good way to draw halo border. +// if (mUserIsMovingPointer) { +// canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], +// (mPointerStrokeWidth /2f) + mPointerHaloWidth + (mPointerHaloBorderWidth / 2f), +// mPointerHaloBorderPaint); +// } + } + } + /** + * Get the progress of the CircularSeekBar. + * @return The progress of the CircularSeekBar. + */ + /** + * Set the progress of the CircularSeekBar. + * If the progress is the same, then any listener will not receive a onProgressChanged event. + * @param progress The progress to set the CircularSeekBar to. + */ + var progress: Float + get() { + val progress = mMax * mProgressDegrees / mTotalCircleDegrees + return if (mIsInNegativeHalf) -progress else progress + } + set(progress) { + if (mProgress != progress) { + if (isNegativeEnabled) { + if (progress < 0) { + mProgress = -progress + mIsInNegativeHalf = true + } else { + mProgress = progress + mIsInNegativeHalf = false + } + } else { + mProgress = progress + } + + mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, false) + + recalculateAll() + invalidate() + } + } + + private fun setProgressBasedOnAngle(angle: Float) { + mPointerPosition = angle + calculateProgressDegrees() + mProgress = mMax * mProgressDegrees / mTotalCircleDegrees + } + + private fun recalculateAll() { + calculateTotalDegrees() + calculatePointerPosition() + calculateProgressDegrees() + resetRects() + resetPaths() + calculatePointerXYPosition() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var height = getDefaultSize(suggestedMinimumHeight, heightMeasureSpec) + var width = getDefaultSize(suggestedMinimumWidth, widthMeasureSpec) + if (height == 0) height = width + if (width == 0) width = height + if (mMaintainEqualCircle) { + val min = Math.min(width, height) + setMeasuredDimension(min, min) + } else { + setMeasuredDimension(width, height) + } + val isHardwareAccelerated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + isHardwareAccelerated && layerType != LAYER_TYPE_SOFTWARE + val hasGlowEffect = !mDisableProgressGlow && !isHardwareAccelerated + + // Set the circle width and height based on the view for the moment + val padding = Math.max( + mCircleStrokeWidth / 2f, + mPointerStrokeWidth / 2 + mPointerHaloWidth + mPointerHaloBorderWidth + ) + + if (hasGlowEffect) PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE else 0f + mCircleHeight = height / 2f - padding + mCircleWidth = width / 2f - padding + + // If it is not set to use custom + if (mCustomRadii) { + // Check to make sure the custom radii are not out of the view. If they are, just use the view values + if (mCircleYRadius - padding < mCircleHeight) { + mCircleHeight = mCircleYRadius - padding + } + if (mCircleXRadius - padding < mCircleWidth) { + mCircleWidth = mCircleXRadius - padding + } + } + if (mMaintainEqualCircle) { // Applies regardless of how the values were determined + val min = Math.min(mCircleHeight, mCircleWidth) + mCircleHeight = min + mCircleWidth = min + } + recalculateAll() + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (mDisablePointer || !isEnabled) return false + + // Convert coordinates to our internal coordinate system + val x = event.x - width / 2 + val y = event.y - height / 2 + + // Get the distance from the center of the circle in terms of x and y + val distanceX = pathCircle.centerX() - x + val distanceY = pathCircle.centerY() - y + + // Get the distance from the center of the circle in terms of a radius + val touchEventRadius = + Math.sqrt(Math.pow(distanceX.toDouble(), 2.0) + Math.pow(distanceY.toDouble(), 2.0)) + .toFloat() + val minimumTouchTarget = + MIN_TOUCH_TARGET_DP * DPTOPX_SCALE // Convert minimum touch target into px + var additionalRadius: Float // Either uses the minimumTouchTarget size or larger if the ring/pointer is larger + additionalRadius = + if (mCircleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the minimumTouchTarget + minimumTouchTarget / 2 + } else { + mCircleStrokeWidth / 2 // Otherwise use the width + } + val outerRadius = Math.max( + mCircleHeight, + mCircleWidth + ) + additionalRadius // Max outer radius of the circle, including the minimumTouchTarget or wheel width + val innerRadius = Math.min( + mCircleHeight, + mCircleWidth + ) - additionalRadius // Min inner radius of the circle, including the minimumTouchTarget or wheel width + additionalRadius = + if (mPointerStrokeWidth < minimumTouchTarget / 2) { // If the pointer radius is less than the minimumTouchTarget, use the minimumTouchTarget + minimumTouchTarget / 2 + } else { + mPointerStrokeWidth // Otherwise use the radius + } + var touchAngle: Float + touchAngle = + (Math.atan2(y.toDouble(), x.toDouble()) / Math.PI * 180 % 360).toFloat() // Verified + touchAngle = if (touchAngle < 0) 360 + touchAngle else touchAngle // Verified + + /* + Represents the clockwise distance from {@code mStartAngle} to the touch angle. + Used when touching the CircularSeekBar. + */ + var cwDistanceFromStart: Float + + /* + Represents the counter-clockwise distance from {@code mStartAngle} to the touch angle. + Used when touching the CircularSeekBar. + */ + val ccwDistanceFromStart: Float + + /* + Represents the clockwise distance from {@code mEndAngle} to the touch angle. + Used when touching the CircularSeekBar. + */ + var cwDistanceFromEnd: Float + + /* + Represents the counter-clockwise distance from {@code mEndAngle} to the touch angle. + Used when touching the CircularSeekBar. + Currently unused, but kept just in case. + */ + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + val ccwDistanceFromEnd: Float + + /* + Represents the clockwise distance from {@code mPointerPosition} to the touch angle. + Used when touching the CircularSeekBar. + */ + var cwDistanceFromPointer: Float + + /* + Represents the counter-clockwise distance from {@code mPointerPosition} to the touch angle. + Used when touching the CircularSeekBar. + */ + val ccwDistanceFromPointer: Float + cwDistanceFromStart = touchAngle - mStartAngle // Verified + cwDistanceFromStart = + if (cwDistanceFromStart < 0) 360f + cwDistanceFromStart else cwDistanceFromStart // Verified + ccwDistanceFromStart = 360f - cwDistanceFromStart // Verified + cwDistanceFromEnd = touchAngle - mEndAngle // Verified + cwDistanceFromEnd = + if (cwDistanceFromEnd < 0) 360f + cwDistanceFromEnd else cwDistanceFromEnd // Verified + ccwDistanceFromEnd = 360f - cwDistanceFromEnd // Verified + when (event.action) { + MotionEvent.ACTION_DOWN -> { + // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched + val pointerRadiusDegrees = (mPointerStrokeWidth * 180 / (Math.PI * Math.max( + mCircleHeight, + mCircleWidth + ))).toFloat() + val pointerDegrees = Math.max(pointerRadiusDegrees, mPointerAngle / 2f) + cwDistanceFromPointer = touchAngle - mPointerPosition + cwDistanceFromPointer = + if (cwDistanceFromPointer < 0) 360f + cwDistanceFromPointer else cwDistanceFromPointer + ccwDistanceFromPointer = 360f - cwDistanceFromPointer + // This is for if the first touch is on the actual pointer. + if (touchEventRadius >= innerRadius && touchEventRadius <= outerRadius && + (cwDistanceFromPointer <= pointerDegrees || ccwDistanceFromPointer <= pointerDegrees) + ) { + setProgressBasedOnAngle(mPointerPosition) + mPointerHaloPaint.alpha = mPointerAlphaOnTouch + mPointerHaloPaint.color = mPointerHaloColorOnTouch + recalculateAll() + invalidate() + mOnCircularSeekBarChangeListener?.onStartTrackingTouch(this) + mUserIsMovingPointer = true + mLockAtEnd = false + mLockAtStart = false + } else if (cwDistanceFromStart > mTotalCircleDegrees) { // If the user is touching outside of the start AND end + mUserIsMovingPointer = false + return false + } else if (touchEventRadius >= innerRadius && touchEventRadius <= outerRadius) { // If the user is touching near the circle + setProgressBasedOnAngle(touchAngle) + mPointerHaloPaint.alpha = mPointerAlphaOnTouch + mPointerHaloPaint.color = mPointerHaloColorOnTouch + recalculateAll() + invalidate() + mOnCircularSeekBarChangeListener?.onStartTrackingTouch(this) + mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + mUserIsMovingPointer = true + mLockAtEnd = false + mLockAtStart = false + } else { // If the user is not touching near the circle + mUserIsMovingPointer = false + return false + } + } + MotionEvent.ACTION_MOVE -> if (mUserIsMovingPointer) { + val smallInCircle = mTotalCircleDegrees / 3f + var cwPointerFromStart = mPointerPosition - mStartAngle + cwPointerFromStart = + if (cwPointerFromStart < 0) cwPointerFromStart + 360f else cwPointerFromStart + val touchOverStart = ccwDistanceFromStart < smallInCircle + val touchOverEnd = cwDistanceFromEnd < smallInCircle + val pointerNearStart = cwPointerFromStart < smallInCircle + val pointerNearEnd = cwPointerFromStart > mTotalCircleDegrees - smallInCircle + val progressNearZero = mProgress < mMax / 3f + val progressNearMax = mProgress > mMax / 3f * 2f + if (progressNearMax) { // logic for end lock. + if (pointerNearStart) { // negative end + mLockAtEnd = touchOverStart + } else if (pointerNearEnd) { // positive end + mLockAtEnd = touchOverEnd + } + } else if (progressNearZero && isNegativeEnabled) { // logic for negative flip + if (touchOverEnd) mIsInNegativeHalf = false else if (touchOverStart) { + mIsInNegativeHalf = true + } + } else if (progressNearZero) { // logic for start lock + if (pointerNearStart) { + mLockAtStart = touchOverStart + } + } + if (mLockAtStart && isLockEnabled) { + // TODO: Add a check if mProgress is already 0, in which case don't call the listener + mProgress = 0f + recalculateAll() + invalidate() + mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + } else if (mLockAtEnd && isLockEnabled) { + mProgress = mMax + recalculateAll() + invalidate() + mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + } else if (mMoveOutsideCircle || touchEventRadius <= outerRadius) { + if (cwDistanceFromStart <= mTotalCircleDegrees) { + setProgressBasedOnAngle(touchAngle) + } + recalculateAll() + invalidate() + mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + } + } else { + return false + } + MotionEvent.ACTION_UP -> { + mPointerHaloPaint.alpha = mPointerAlpha + mPointerHaloPaint.color = mPointerHaloColor + if (mUserIsMovingPointer) { + mUserIsMovingPointer = false + invalidate() + mOnCircularSeekBarChangeListener?.onStopTrackingTouch(this) + } else { + return false + } + } + MotionEvent.ACTION_CANCEL -> { + mPointerHaloPaint.alpha = mPointerAlpha + mPointerHaloPaint.color = mPointerHaloColor + mUserIsMovingPointer = false + invalidate() + } + } + if (event.action == MotionEvent.ACTION_MOVE && parent != null) { + parent.requestDisallowInterceptTouchEvent(true) + } + return true + } + + override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + val state = Bundle() + state.putParcelable("PARENT", superState) + state.putFloat("MAX", mMax) + state.putFloat("PROGRESS", mProgress) + state.putInt("mCircleColor", mCircleColor) + state.putInt("mCircleProgressColor", mCircleProgressColor) + state.putInt("mPointerColor", mPointerColor) + state.putInt("mPointerHaloColor", mPointerHaloColor) + state.putInt("mPointerHaloColorOnTouch", mPointerHaloColorOnTouch) + state.putInt("mPointerAlpha", mPointerAlpha) + state.putInt("mPointerAlphaOnTouch", mPointerAlphaOnTouch) + state.putFloat("mPointerAngle", mPointerAngle) + state.putBoolean("mDisablePointer", mDisablePointer) + state.putBoolean("mLockEnabled", isLockEnabled) + state.putBoolean("mNegativeEnabled", isNegativeEnabled) + state.putBoolean("mDisableProgressGlow", mDisableProgressGlow) + state.putBoolean("mIsInNegativeHalf", mIsInNegativeHalf) + state.putInt("mCircleStyle", mCircleStyle.ordinal) + state.putBoolean("mHideProgressWhenEmpty", mHideProgressWhenEmpty) + return state + } + + override fun onRestoreInstanceState(state: Parcelable) { + val savedState = state as Bundle + val superState = savedState.getParcelable("PARENT") + super.onRestoreInstanceState(superState) + mMax = savedState.getFloat("MAX") + mProgress = savedState.getFloat("PROGRESS") + mCircleColor = savedState.getInt("mCircleColor") + mCircleProgressColor = savedState.getInt("mCircleProgressColor") + mPointerColor = savedState.getInt("mPointerColor") + mPointerHaloColor = savedState.getInt("mPointerHaloColor") + mPointerHaloColorOnTouch = savedState.getInt("mPointerHaloColorOnTouch") + mPointerAlpha = savedState.getInt("mPointerAlpha") + mPointerAlphaOnTouch = savedState.getInt("mPointerAlphaOnTouch") + mPointerAngle = savedState.getFloat("mPointerAngle") + mDisablePointer = savedState.getBoolean("mDisablePointer") + isLockEnabled = savedState.getBoolean("mLockEnabled") + isNegativeEnabled = savedState.getBoolean("mNegativeEnabled") + mDisableProgressGlow = savedState.getBoolean("mDisableProgressGlow") + mIsInNegativeHalf = savedState.getBoolean("mIsInNegativeHalf") + mCircleStyle = Cap.values()[savedState.getInt("mCircleStyle")] + mHideProgressWhenEmpty = savedState.getBoolean("mHideProgressWhenEmpty") + initPaints() + recalculateAll() + } + + fun setOnSeekBarChangeListener(l: OnCircularSeekBarChangeListener?) { + mOnCircularSeekBarChangeListener = l + } + + /** + * Listener for the CircularSeekBar. Implements the same methods as the normal OnSeekBarChangeListener. + */ + interface OnCircularSeekBarChangeListener { + fun onProgressChanged( + circularSeekBar: CircularSeekBarV2?, + progress: Float, + fromUser: Boolean + ) + + fun onStopTrackingTouch(seekBar: CircularSeekBarV2?) + fun onStartTrackingTouch(seekBar: CircularSeekBarV2?) + } + + companion object { + /** + * Minimum touch target size in DP. 48dp is the Android design recommendation + */ + private const val MIN_TOUCH_TARGET_DP = 48f + + /** + * For some case we need the degree to have small bias to avoid overflow. + */ + private const val SMALL_DEGREE_BIAS = .1f + + /** + * Radius of progress glow, in dp unit. + */ + private const val PROGRESS_GLOW_RADIUS_DP = 5f + + // Default values + private val DEFAULT_CIRCLE_STYLE = Cap.ROUND.ordinal + private const val DEFAULT_CIRCLE_X_RADIUS = 30f + private const val DEFAULT_CIRCLE_Y_RADIUS = 30f + private const val DEFAULT_POINTER_STROKE_WIDTH = 14f + private const val DEFAULT_POINTER_HALO_WIDTH = 6f + private const val DEFAULT_POINTER_HALO_BORDER_WIDTH = 0f + private const val DEFAULT_CIRCLE_STROKE_WIDTH = 5f + private const val DEFAULT_START_ANGLE = 270f // Geometric (clockwise, relative to 3 o'clock) + private const val DEFAULT_END_ANGLE = 270f // Geometric (clockwise, relative to 3 o'clock) + private const val DEFAULT_POINTER_ANGLE = 0f + private const val DEFAULT_MAX = 100 + private const val DEFAULT_PROGRESS = 0 + private const val DEFAULT_CIRCLE_COLOR = Color.DKGRAY + private val DEFAULT_CIRCLE_PROGRESS_COLOR = Color.argb(235, 74, 138, 255) + private val DEFAULT_POINTER_COLOR = Color.argb(235, 74, 138, 255) + private val DEFAULT_POINTER_HALO_COLOR = Color.argb(135, 74, 138, 255) + private val DEFAULT_POINTER_HALO_COLOR_ONTOUCH = Color.argb(135, 74, 138, 255) + private const val DEFAULT_CIRCLE_FILL_COLOR = Color.TRANSPARENT + private const val DEFAULT_POINTER_ALPHA = 135 + private const val DEFAULT_POINTER_ALPHA_ONTOUCH = 100 + private const val DEFAULT_USE_CUSTOM_RADII = false + private const val DEFAULT_MAINTAIN_EQUAL_CIRCLE = true + private const val DEFAULT_MOVE_OUTSIDE_CIRCLE = false + private const val DEFAULT_LOCK_ENABLED = true + private const val DEFAULT_DISABLE_POINTER = false + private const val DEFAULT_NEGATIVE_ENABLED = false + private const val DEFAULT_DISABLE_PROGRESS_GLOW = true + private const val DEFAULT_CS_HIDE_PROGRESS_WHEN_EMPTY = false + } +} \ No newline at end of file diff --git a/circularSeekBar/src/main/res/values/attrs.xml b/circularSeekBar/src/main/res/values/attrs.xml index 72151cd8..108b67d5 100644 --- a/circularSeekBar/src/main/res/values/attrs.xml +++ b/circularSeekBar/src/main/res/values/attrs.xml @@ -34,5 +34,4 @@ - From d931014726e39fb157cff639d230fd3ec1bc5e95 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 11 May 2022 14:30:22 -0400 Subject: [PATCH 05/17] Deletes old CircularSeekBar.java Renames CircularSeekBarV2.kt to CircularSeekBar.kt --- .../app/circularseekbar/MainActivity.kt | 12 +- app/src/main/res/layout/activity_main.xml | 10 +- .../lib/circularseekbar/CircularSeekBar.java | 1317 ----------------- ...ircularSeekBarV2.kt => CircularSeekBar.kt} | 10 +- 4 files changed, 15 insertions(+), 1334 deletions(-) delete mode 100644 circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.java rename circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/{CircularSeekBarV2.kt => CircularSeekBar.kt} (99%) diff --git a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt index 73269ad7..6efa5e0a 100644 --- a/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt +++ b/app/src/main/java/me/tankery/app/circularseekbar/MainActivity.kt @@ -7,7 +7,7 @@ import android.util.Log import android.widget.TextView import me.tankery.app.circularseekbar.R.id import me.tankery.app.circularseekbar.R.layout -import me.tankery.lib.circularseekbar.CircularSeekBarV2 +import me.tankery.lib.circularseekbar.CircularSeekBar class MainActivity : Activity() { @@ -18,23 +18,23 @@ class MainActivity : Activity() { val textEvent = findViewById(id.text_event) val textProgress = findViewById(id.text_progress) - val seekBar = findViewById(id.seek_bar) + val seekBar = findViewById(id.seek_bar) seekBar.setOnSeekBarChangeListener(object : - CircularSeekBarV2.OnCircularSeekBarChangeListener { - override fun onProgressChanged(circularSeekBar: CircularSeekBarV2?, progress: Float, fromUser: Boolean) { + CircularSeekBar.OnCircularSeekBarChangeListener { + override fun onProgressChanged(circularSeekBar: CircularSeekBar?, progress: Float, fromUser: Boolean) { val message = String.format("Progress changed to %.2f, fromUser %s", progress, fromUser) Log.d("Main", message) textProgress.text = message } - override fun onStopTrackingTouch(seekBar: CircularSeekBarV2?) { + override fun onStopTrackingTouch(seekBar: CircularSeekBar?) { Log.d("Main", "onStopTrackingTouch") textEvent.text = "" } @SuppressLint("SetTextI18n") - override fun onStartTrackingTouch(seekBar: CircularSeekBarV2?) { + override fun onStartTrackingTouch(seekBar: CircularSeekBar?) { Log.d("Main", "onStartTrackingTouch") textEvent.text = "touched | " } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e142fd0d..0ed82c8b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,7 +15,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - - 255 || mPointerAlphaOnTouch < 0) { - mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH; - } - - mMax = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_max, DEFAULT_MAX); - mProgress = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_progress, DEFAULT_PROGRESS); - mCustomRadii = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_use_custom_radii, DEFAULT_USE_CUSTOM_RADII); - mMaintainEqualCircle = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_maintain_equal_circle, DEFAULT_MAINTAIN_EQUAL_CIRCLE); - mMoveOutsideCircle = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_move_outside_circle, DEFAULT_MOVE_OUTSIDE_CIRCLE); - mLockEnabled = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_lock_enabled, DEFAULT_LOCK_ENABLED); - mDisablePointer = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_disable_pointer, DEFAULT_DISABLE_POINTER); - mNegativeEnabled = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_negative_enabled, DEFAULT_NEGATIVE_ENABLED); - mIsInNegativeHalf = false; - mDisableProgressGlow = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_disable_progress_glow, DEFAULT_DISABLE_PROGRESS_GLOW); - mHideProgressWhenEmpty = attrArray.getBoolean(R.styleable.cs_CircularSeekBar_cs_hide_progress_when_empty, DEFAULT_CS_HIDE_PROGRESS_WHEN_EMPTY); - - // Modulo 360 right now to avoid constant conversion - mStartAngle = ((360f + (attrArray.getFloat((R.styleable.cs_CircularSeekBar_cs_start_angle), DEFAULT_START_ANGLE) % 360f)) % 360f); - mEndAngle = ((360f + (attrArray.getFloat((R.styleable.cs_CircularSeekBar_cs_end_angle), DEFAULT_END_ANGLE) % 360f)) % 360f); - - // Disable negative progress if is semi-oval. - if (mStartAngle != mEndAngle) { - mNegativeEnabled = false; - } - - if (mStartAngle % 360f == mEndAngle % 360f) { - //mStartAngle = mStartAngle + 1f; - mEndAngle = mEndAngle - SMALL_DEGREE_BIAS; - } - - // Modulo 360 right now to avoid constant conversion - mPointerAngle = ((360f + (attrArray.getFloat((R.styleable.cs_CircularSeekBar_cs_pointer_angle), DEFAULT_POINTER_ANGLE) % 360f)) % 360f); - if (mPointerAngle == 0f) { - mPointerAngle = SMALL_DEGREE_BIAS; - } - - if (mDisablePointer) { - mPointerStrokeWidth = 0; - mPointerHaloWidth = 0; - mPointerHaloBorderWidth = 0; - } - - } - - /** - * Initializes the {@code Paint} objects with the appropriate styles. - */ - private void initPaints() { - mCirclePaint = new Paint(); - mCirclePaint.setAntiAlias(true); - mCirclePaint.setDither(true); - mCirclePaint.setColor(mCircleColor); - mCirclePaint.setStrokeWidth(mCircleStrokeWidth); - mCirclePaint.setStyle(Paint.Style.STROKE); - mCirclePaint.setStrokeJoin(Paint.Join.ROUND); - mCirclePaint.setStrokeCap(mCircleStyle); - - mCircleFillPaint = new Paint(); - mCircleFillPaint.setAntiAlias(true); - mCircleFillPaint.setDither(true); - mCircleFillPaint.setColor(mCircleFillColor); - mCircleFillPaint.setStyle(Paint.Style.FILL); - - mCircleProgressPaint = new Paint(); - mCircleProgressPaint.setAntiAlias(true); - mCircleProgressPaint.setDither(true); - mCircleProgressPaint.setColor(mCircleProgressColor); - mCircleProgressPaint.setStrokeWidth(mCircleStrokeWidth); - mCircleProgressPaint.setStyle(Paint.Style.STROKE); - mCircleProgressPaint.setStrokeJoin(Paint.Join.ROUND); - mCircleProgressPaint.setStrokeCap(mCircleStyle); - - if (!mDisableProgressGlow) { - mCircleProgressGlowPaint = new Paint(); - mCircleProgressGlowPaint.set(mCircleProgressPaint); - mCircleProgressGlowPaint.setMaskFilter(new BlurMaskFilter((PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE), BlurMaskFilter.Blur.NORMAL)); - } - - mPointerPaint = new Paint(); - mPointerPaint.setAntiAlias(true); - mPointerPaint.setDither(true); - mPointerPaint.setColor(mPointerColor); - mPointerPaint.setStrokeWidth(mPointerStrokeWidth); - mPointerPaint.setStyle(Paint.Style.STROKE); - mPointerPaint.setStrokeJoin(Paint.Join.ROUND); - mPointerPaint.setStrokeCap(mCircleStyle); - - mPointerHaloPaint = new Paint(); - mPointerHaloPaint.set(mPointerPaint); - mPointerHaloPaint.setColor(mPointerHaloColor); - mPointerHaloPaint.setAlpha(mPointerAlpha); - mPointerHaloPaint.setStrokeWidth(mPointerStrokeWidth + mPointerHaloWidth * 2f); - - mPointerHaloBorderPaint = new Paint(); - mPointerHaloBorderPaint.set(mPointerPaint); - mPointerHaloBorderPaint.setStrokeWidth(mPointerHaloBorderWidth); - mPointerHaloBorderPaint.setStyle(Paint.Style.STROKE); - - } - - /** - * Calculates the total degrees between mStartAngle and mEndAngle, and sets mTotalCircleDegrees - * to this value. - */ - private void calculateTotalDegrees() { - mTotalCircleDegrees = (360f - (mStartAngle - mEndAngle)) % 360f; // Length of the entire circle/arc - if (mTotalCircleDegrees <= 0f) { - mTotalCircleDegrees = 360f; - } - } - - /** - * Calculate the degrees that the progress represents. Also called the sweep angle. - * Sets mProgressDegrees to that value. - */ - private void calculateProgressDegrees() { - mProgressDegrees = mIsInNegativeHalf ? mStartAngle - mPointerPosition : mPointerPosition - mStartAngle; // Verified - mProgressDegrees = (mProgressDegrees < 0 ? 360f + mProgressDegrees : mProgressDegrees); // Verified - } - - /** - * Calculate the pointer position (and the end of the progress arc) in degrees. - * Sets mPointerPosition to that value. - */ - private void calculatePointerPosition() { - float progressPercent = mProgress / mMax; - float progressDegree = (progressPercent * mTotalCircleDegrees); - mPointerPosition = mStartAngle + (mIsInNegativeHalf ? -progressDegree : progressDegree); - mPointerPosition = (mPointerPosition < 0 ? 360f + mPointerPosition : mPointerPosition) % 360f; - } - - private void calculatePointerXYPosition() { - PathMeasure pm = new PathMeasure(mCircleProgressPath, false); - boolean returnValue = pm.getPosTan(pm.getLength(), mPointerPositionXY, null); - if (!returnValue) { - pm = new PathMeasure(mCirclePath, false); - pm.getPosTan(0, mPointerPositionXY, null); - } - } - - /** - * Initialize the {@code Path} objects. - */ - private void initPaths() { - mCirclePath = new Path(); - mCircleProgressPath = new Path(); - mCirclePonterPath = new Path(); - } - - /** - * Reset the {@code Path} objects with the appropriate values. - */ - private void resetPaths() { - if (mIsInNegativeHalf) { - mCirclePath.reset(); - mCirclePath.addArc(mCircleRectF, mStartAngle - mTotalCircleDegrees, mTotalCircleDegrees); - - // beside progress path it self, we also draw a extend arc to math the pointer arc. - float extendStart = mStartAngle - mProgressDegrees - mPointerAngle / 2.0f; - float extendDegrees = mProgressDegrees + mPointerAngle; - if (extendDegrees >= 360f) { - extendDegrees = 360f - SMALL_DEGREE_BIAS; - } - mCircleProgressPath.reset(); - mCircleProgressPath.addArc(mCircleRectF, extendStart, extendDegrees); - - float pointerStart = mPointerPosition - mPointerAngle / 2.0f; - mCirclePonterPath.reset(); - mCirclePonterPath.addArc(mCircleRectF, pointerStart, mPointerAngle); - } else { - mCirclePath.reset(); - mCirclePath.addArc(mCircleRectF, mStartAngle, mTotalCircleDegrees); - - // beside progress path it self, we also draw a extend arc to math the pointer arc. - float extendStart = mStartAngle - mPointerAngle / 2.0f; - float extendDegrees = mProgressDegrees + mPointerAngle; - if (extendDegrees >= 360f) { - extendDegrees = 360f - SMALL_DEGREE_BIAS; - } - mCircleProgressPath.reset(); - mCircleProgressPath.addArc(mCircleRectF, extendStart, extendDegrees); - - float pointerStart = mPointerPosition - mPointerAngle / 2.0f; - mCirclePonterPath.reset(); - mCirclePonterPath.addArc(mCircleRectF, pointerStart, mPointerAngle); - } - } - - /** - * Initialize the {@code RectF} objects with the appropriate values. - */ - private void resetRects() { - mCircleRectF.set(-mCircleWidth, -mCircleHeight, mCircleWidth, mCircleHeight); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - canvas.translate(getWidth() / 2f, getHeight() / 2f); - - canvas.drawPath(mCirclePath, mCircleFillPaint); - canvas.drawPath(mCirclePath, mCirclePaint); - - boolean ableToGoNegative = mNegativeEnabled && Math.abs(mTotalCircleDegrees - 360f) < SMALL_DEGREE_BIAS * 2; - // Hide progress bar when progress is 0 - // Also make sure we still draw progress when has pointer or able to go negative - boolean shouldHideProgress = mHideProgressWhenEmpty && mProgressDegrees == 0f && - mDisablePointer && !ableToGoNegative; - - if (!shouldHideProgress) { - if (!mDisableProgressGlow) { - canvas.drawPath(mCircleProgressPath, mCircleProgressGlowPaint); - } - - canvas.drawPath(mCircleProgressPath, mCircleProgressPaint); - } - - if (!mDisablePointer) { - if (mUserIsMovingPointer) { - canvas.drawPath(mCirclePonterPath, mPointerHaloPaint); - } - canvas.drawPath(mCirclePonterPath, mPointerPaint); - // TODO, find a good way to draw halo border. -// if (mUserIsMovingPointer) { -// canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], -// (mPointerStrokeWidth /2f) + mPointerHaloWidth + (mPointerHaloBorderWidth / 2f), -// mPointerHaloBorderPaint); -// } - } - } - - /** - * Get the progress of the CircularSeekBar. - * @return The progress of the CircularSeekBar. - */ - public float getProgress() { - float progress = mMax * mProgressDegrees / mTotalCircleDegrees; - return mIsInNegativeHalf ? -progress : progress; - } - - /** - * Set the progress of the CircularSeekBar. - * If the progress is the same, then any listener will not receive a onProgressChanged event. - * @param progress The progress to set the CircularSeekBar to. - */ - public void setProgress(float progress) { - if (mProgress != progress) { - if (mNegativeEnabled) { - if (progress < 0) { - mProgress = -progress; - mIsInNegativeHalf = true; - } else { - mProgress = progress; - mIsInNegativeHalf = false; - } - } else { - mProgress = progress; - } - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onProgressChanged(this, progress, false); - } - - recalculateAll(); - invalidate(); - } - } - - private void setProgressBasedOnAngle(float angle) { - mPointerPosition = angle; - calculateProgressDegrees(); - mProgress = mMax * mProgressDegrees / mTotalCircleDegrees; - } - - private void recalculateAll() { - calculateTotalDegrees(); - calculatePointerPosition(); - calculateProgressDegrees(); - - resetRects(); - - resetPaths(); - - calculatePointerXYPosition(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec); - int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); - if (height == 0) height = width; - if (width == 0) width = height; - if (mMaintainEqualCircle) { - int min = Math.min(width, height); - setMeasuredDimension(min, min); - } else { - setMeasuredDimension(width, height); - } - - boolean isHardwareAccelerated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && - isHardwareAccelerated() && getLayerType() != View.LAYER_TYPE_SOFTWARE; - boolean hasGlowEffect = !mDisableProgressGlow && !isHardwareAccelerated; - - // Set the circle width and height based on the view for the moment - float padding = Math.max(mCircleStrokeWidth / 2f, - mPointerStrokeWidth / 2 + mPointerHaloWidth + mPointerHaloBorderWidth) + - (hasGlowEffect ? PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE : 0f); - mCircleHeight = height / 2f - padding; - mCircleWidth = width / 2f - padding; - - // If it is not set to use custom - if (mCustomRadii) { - // Check to make sure the custom radii are not out of the view. If they are, just use the view values - if ((mCircleYRadius - padding) < mCircleHeight) { - mCircleHeight = mCircleYRadius - padding; - } - - if ((mCircleXRadius - padding) < mCircleWidth) { - mCircleWidth = mCircleXRadius - padding; - } - } - - if (mMaintainEqualCircle) { // Applies regardless of how the values were determined - float min = Math.min(mCircleHeight, mCircleWidth); - mCircleHeight = min; - mCircleWidth = min; - } - - recalculateAll(); - } - - public boolean isLockEnabled() { - return mLockEnabled; - } - - public void setLockEnabled(boolean lockEnabled) { - this.mLockEnabled = lockEnabled; - } - - public boolean isNegativeEnabled() { - return mNegativeEnabled; - } - - public void setNegativeEnabled(boolean negativeEnabled) { - this.mNegativeEnabled = negativeEnabled; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mDisablePointer || !isEnabled()) - return false; - - // Convert coordinates to our internal coordinate system - float x = event.getX() - getWidth() / 2; - float y = event.getY() - getHeight() / 2; - - // Get the distance from the center of the circle in terms of x and y - float distanceX = mCircleRectF.centerX() - x; - float distanceY = mCircleRectF.centerY() - y; - - // Get the distance from the center of the circle in terms of a radius - float touchEventRadius = (float) Math.sqrt((Math.pow(distanceX, 2) + Math.pow(distanceY, 2))); - - float minimumTouchTarget = MIN_TOUCH_TARGET_DP * DPTOPX_SCALE; // Convert minimum touch target into px - float additionalRadius; // Either uses the minimumTouchTarget size or larger if the ring/pointer is larger - - if (mCircleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the minimumTouchTarget - additionalRadius = minimumTouchTarget / 2; - } - else { - additionalRadius = mCircleStrokeWidth / 2; // Otherwise use the width - } - float outerRadius = Math.max(mCircleHeight, mCircleWidth) + additionalRadius; // Max outer radius of the circle, including the minimumTouchTarget or wheel width - float innerRadius = Math.min(mCircleHeight, mCircleWidth) - additionalRadius; // Min inner radius of the circle, including the minimumTouchTarget or wheel width - - if (mPointerStrokeWidth < (minimumTouchTarget / 2)) { // If the pointer radius is less than the minimumTouchTarget, use the minimumTouchTarget - additionalRadius = minimumTouchTarget / 2; - } - else { - additionalRadius = mPointerStrokeWidth; // Otherwise use the radius - } - - float touchAngle; - touchAngle = (float) ((Math.atan2(y, x) / Math.PI * 180) % 360); // Verified - touchAngle = (touchAngle < 0 ? 360 + touchAngle : touchAngle); // Verified - - /* - Represents the clockwise distance from {@code mStartAngle} to the touch angle. - Used when touching the CircularSeekBar. - */ - float cwDistanceFromStart; - - /* - Represents the counter-clockwise distance from {@code mStartAngle} to the touch angle. - Used when touching the CircularSeekBar. - */ - float ccwDistanceFromStart; - - /* - Represents the clockwise distance from {@code mEndAngle} to the touch angle. - Used when touching the CircularSeekBar. - */ - float cwDistanceFromEnd; - - /* - Represents the counter-clockwise distance from {@code mEndAngle} to the touch angle. - Used when touching the CircularSeekBar. - Currently unused, but kept just in case. - */ - float ccwDistanceFromEnd; - - /* - Represents the clockwise distance from {@code mPointerPosition} to the touch angle. - Used when touching the CircularSeekBar. - */ - float cwDistanceFromPointer; - - /* - Represents the counter-clockwise distance from {@code mPointerPosition} to the touch angle. - Used when touching the CircularSeekBar. - */ - float ccwDistanceFromPointer; - - cwDistanceFromStart = touchAngle - mStartAngle; // Verified - cwDistanceFromStart = (cwDistanceFromStart < 0 ? 360f + cwDistanceFromStart : cwDistanceFromStart); // Verified - ccwDistanceFromStart = 360f - cwDistanceFromStart; // Verified - - cwDistanceFromEnd = touchAngle - mEndAngle; // Verified - cwDistanceFromEnd = (cwDistanceFromEnd < 0 ? 360f + cwDistanceFromEnd : cwDistanceFromEnd); // Verified - ccwDistanceFromEnd = 360f - cwDistanceFromEnd; // Verified - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched - float pointerRadiusDegrees = (float) ((mPointerStrokeWidth * 180) / (Math.PI * Math.max(mCircleHeight, mCircleWidth))); - float pointerDegrees = Math.max( pointerRadiusDegrees, (mPointerAngle / 2f) ); - cwDistanceFromPointer = touchAngle - mPointerPosition; - cwDistanceFromPointer = (cwDistanceFromPointer < 0 ? 360f + cwDistanceFromPointer : cwDistanceFromPointer); - ccwDistanceFromPointer = 360f - cwDistanceFromPointer; - // This is for if the first touch is on the actual pointer. - if ( ((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) && - ((cwDistanceFromPointer <= pointerDegrees) || (ccwDistanceFromPointer <= pointerDegrees)) ) { - setProgressBasedOnAngle(mPointerPosition); - mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch); - mPointerHaloPaint.setColor(mPointerHaloColorOnTouch); - recalculateAll(); - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onStartTrackingTouch(this); - } - mUserIsMovingPointer = true; - mLockAtEnd = false; - mLockAtStart = false; - } else if (cwDistanceFromStart > mTotalCircleDegrees) { // If the user is touching outside of the start AND end - mUserIsMovingPointer = false; - return false; - } else if ((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) { // If the user is touching near the circle - setProgressBasedOnAngle(touchAngle); - mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch); - mPointerHaloPaint.setColor(mPointerHaloColorOnTouch); - recalculateAll(); - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onStartTrackingTouch(this); - mOnCircularSeekBarChangeListener.onProgressChanged(this, getProgress(), true); - } - mUserIsMovingPointer = true; - mLockAtEnd = false; - mLockAtStart = false; - } else { // If the user is not touching near the circle - mUserIsMovingPointer = false; - return false; - } - break; - case MotionEvent.ACTION_MOVE: - if (mUserIsMovingPointer) { - float smallInCircle = mTotalCircleDegrees / 3f; - float cwPointerFromStart = mPointerPosition - mStartAngle; - cwPointerFromStart = cwPointerFromStart < 0 ? cwPointerFromStart + 360f : cwPointerFromStart; - - boolean touchOverStart = ccwDistanceFromStart < smallInCircle; - boolean touchOverEnd = cwDistanceFromEnd < smallInCircle; - boolean pointerNearStart = cwPointerFromStart < smallInCircle; - boolean pointerNearEnd = cwPointerFromStart > (mTotalCircleDegrees - smallInCircle); - boolean progressNearZero = mProgress < mMax / 3f; - boolean progressNearMax = mProgress > mMax / 3f * 2f; - - if (progressNearMax) { // logic for end lock. - if (pointerNearStart) { // negative end - mLockAtEnd = touchOverStart; - } else if (pointerNearEnd) { // positive end - mLockAtEnd = touchOverEnd; - } - } else if (progressNearZero && mNegativeEnabled) { // logic for negative flip - if (touchOverEnd) - mIsInNegativeHalf = false; - else if (touchOverStart) { - mIsInNegativeHalf = true; - } - } else if (progressNearZero) { // logic for start lock - if (pointerNearStart) { - mLockAtStart = touchOverStart; - } - } - - if (mLockAtStart && mLockEnabled) { - // TODO: Add a check if mProgress is already 0, in which case don't call the listener - mProgress = 0; - recalculateAll(); - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onProgressChanged(this, getProgress(), true); - } - - } else if (mLockAtEnd && mLockEnabled) { - mProgress = mMax; - recalculateAll(); - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onProgressChanged(this, getProgress(), true); - } - } else if ((mMoveOutsideCircle) || (touchEventRadius <= outerRadius)) { - if (!(cwDistanceFromStart > mTotalCircleDegrees)) { - setProgressBasedOnAngle(touchAngle); - } - recalculateAll(); - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onProgressChanged(this, getProgress(), true); - } - } else { - break; - } - - } else { - return false; - } - break; - case MotionEvent.ACTION_UP: - mPointerHaloPaint.setAlpha(mPointerAlpha); - mPointerHaloPaint.setColor(mPointerHaloColor); - if (mUserIsMovingPointer) { - mUserIsMovingPointer = false; - invalidate(); - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onStopTrackingTouch(this); - } - } else { - return false; - } - break; - case MotionEvent.ACTION_CANCEL: // Used when the parent view intercepts touches for things like scrolling - mPointerHaloPaint.setAlpha(mPointerAlpha); - mPointerHaloPaint.setColor(mPointerHaloColor); - mUserIsMovingPointer = false; - invalidate(); - break; - } - - if (event.getAction() == MotionEvent.ACTION_MOVE && getParent() != null) { - getParent().requestDisallowInterceptTouchEvent(true); - } - - return true; - } - - private void init(AttributeSet attrs, int defStyle) { - final TypedArray attrArray = getContext().obtainStyledAttributes(attrs, R.styleable.cs_CircularSeekBar, defStyle, 0); - - initAttributes(attrArray); - - attrArray.recycle(); - - initPaints(); - initPaths(); - } - - public CircularSeekBar(Context context) { - super(context); - init(null, 0); - } - - public CircularSeekBar(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, 0); - } - - public CircularSeekBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(attrs, defStyle); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - - Bundle state = new Bundle(); - state.putParcelable("PARENT", superState); - state.putFloat("MAX", mMax); - state.putFloat("PROGRESS", mProgress); - state.putInt("mCircleColor", mCircleColor); - state.putInt("mCircleProgressColor", mCircleProgressColor); - state.putInt("mPointerColor", mPointerColor); - state.putInt("mPointerHaloColor", mPointerHaloColor); - state.putInt("mPointerHaloColorOnTouch", mPointerHaloColorOnTouch); - state.putInt("mPointerAlpha", mPointerAlpha); - state.putInt("mPointerAlphaOnTouch", mPointerAlphaOnTouch); - state.putFloat("mPointerAngle", mPointerAngle); - state.putBoolean("mDisablePointer", mDisablePointer); - state.putBoolean("mLockEnabled", mLockEnabled); - state.putBoolean("mNegativeEnabled", mNegativeEnabled); - state.putBoolean("mDisableProgressGlow", mDisableProgressGlow); - state.putBoolean("mIsInNegativeHalf", mIsInNegativeHalf); - state.putInt("mCircleStyle", mCircleStyle.ordinal()); - state.putBoolean("mHideProgressWhenEmpty", mHideProgressWhenEmpty); - - return state; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - Bundle savedState = (Bundle) state; - - Parcelable superState = savedState.getParcelable("PARENT"); - super.onRestoreInstanceState(superState); - - mMax = savedState.getFloat("MAX"); - mProgress = savedState.getFloat("PROGRESS"); - mCircleColor = savedState.getInt("mCircleColor"); - mCircleProgressColor = savedState.getInt("mCircleProgressColor"); - mPointerColor = savedState.getInt("mPointerColor"); - mPointerHaloColor = savedState.getInt("mPointerHaloColor"); - mPointerHaloColorOnTouch = savedState.getInt("mPointerHaloColorOnTouch"); - mPointerAlpha = savedState.getInt("mPointerAlpha"); - mPointerAlphaOnTouch = savedState.getInt("mPointerAlphaOnTouch"); - mPointerAngle = savedState.getFloat("mPointerAngle"); - mDisablePointer = savedState.getBoolean("mDisablePointer"); - mLockEnabled = savedState.getBoolean("mLockEnabled"); - mNegativeEnabled = savedState.getBoolean("mNegativeEnabled"); - mDisableProgressGlow = savedState.getBoolean("mDisableProgressGlow"); - mIsInNegativeHalf = savedState.getBoolean("mIsInNegativeHalf"); - mCircleStyle = Paint.Cap.values()[savedState.getInt("mCircleStyle")]; - mHideProgressWhenEmpty = savedState.getBoolean("mHideProgressWhenEmpty"); - - initPaints(); - - recalculateAll(); - } - - - public void setOnSeekBarChangeListener(OnCircularSeekBarChangeListener l) { - mOnCircularSeekBarChangeListener = l; - } - - /** - * Listener for the CircularSeekBar. Implements the same methods as the normal OnSeekBarChangeListener. - */ - public interface OnCircularSeekBarChangeListener { - - public abstract void onProgressChanged(CircularSeekBar circularSeekBar, float progress, boolean fromUser); - - public abstract void onStopTrackingTouch(CircularSeekBar seekBar); - - public abstract void onStartTrackingTouch(CircularSeekBar seekBar); - } - - public Paint.Cap getCircleStyle() { - return mCircleStyle; - } - - public void setCircleStyle(Paint.Cap style) { - mCircleStyle = style; - initPaints(); - recalculateAll(); - invalidate(); - } - - /** - * Sets the circle stroke width. - * @param width the width of the circle - */ - public void setCircleStrokeWidth(float width) { - mCircleStrokeWidth = width; - initPaints(); - recalculateAll(); - invalidate(); - } - public float getCircleStrokeWidth() { - return mCircleStrokeWidth; - } - - public float getEndAngle() { - return mEndAngle; - } - - public void setEndAngle(float angle) { - mEndAngle = angle; - if (mStartAngle % 360f == mEndAngle % 360f) { - //mStartAngle = mStartAngle + 1f; - mEndAngle = mEndAngle - SMALL_DEGREE_BIAS; - } - recalculateAll(); - invalidate(); - } - - public float getStartAngle() { - return mStartAngle; - } - - public void setStartAngle(float angle) { - mStartAngle = angle; - if (mStartAngle % 360f == mEndAngle % 360f) { - //mStartAngle = mStartAngle + 1f; - mEndAngle = mEndAngle - SMALL_DEGREE_BIAS; - } - recalculateAll(); - invalidate(); - } - - /** - * Sets the circle color. - * @param color the color of the circle - */ - public void setCircleColor(int color) { - mCircleColor = color; - mCirclePaint.setColor(mCircleColor); - invalidate(); - } - - /** - * Gets the circle color. - * @return An integer color value for the circle - */ - public int getCircleColor() { - return mCircleColor; - } - - /** - * Sets the pointer pointer stroke width. - * @param width the width of the pointer - */ - public void setPointerStrokeWidth(float width) { - mPointerStrokeWidth = width; - initPaints(); - recalculateAll(); - invalidate(); - } - public float getPointerStrokeWidth() { - return mPointerStrokeWidth; - } - - /** - * Sets the circle progress color. - * @param color the color of the circle progress - */ - public void setCircleProgressColor(int color) { - mCircleProgressColor = color; - mCircleProgressPaint.setColor(mCircleProgressColor); - invalidate(); - } - - /** - * Gets the circle progress color. - * @return An integer color value for the circle progress - */ - public int getCircleProgressColor() { - return mCircleProgressColor; - } - - /** - * Sets the pointer color. - * @param color the color of the pointer - */ - public void setPointerColor(int color) { - mPointerColor = color; - mPointerPaint.setColor(mPointerColor); - invalidate(); - } - - /** - * Gets the pointer color. - * @return An integer color value for the pointer - */ - public int getPointerColor() { - return mPointerColor; - } - - /** - * Sets the pointer halo color. - * @param color the color of the pointer halo - */ - public void setPointerHaloColor(int color) { - mPointerHaloColor = color; - mPointerHaloPaint.setColor(mPointerHaloColor); - invalidate(); - } - - /** - * Gets the pointer halo color. - * @return An integer color value for the pointer halo - */ - public int getPointerHaloColor() { - return mPointerHaloColor; - } - - /** - * Sets the pointer alpha. - * @param alpha the alpha of the pointer - */ - public void setPointerAlpha(int alpha) { - if (alpha >=0 && alpha <= 255) { - mPointerAlpha = alpha; - mPointerHaloPaint.setAlpha(mPointerAlpha); - invalidate(); - } - } - - /** - * Gets the pointer alpha value. - * @return An integer alpha value for the pointer (0..255) - */ - public int getPointerAlpha() { - return mPointerAlpha; - } - - /** - * Sets the pointer alpha when touched. - * @param alpha the alpha of the pointer (0..255) when touched - */ - public void setPointerAlphaOnTouch(int alpha) { - if (alpha >=0 && alpha <= 255) { - mPointerAlphaOnTouch = alpha; - } - } - - /** - * Gets the pointer alpha value when touched. - * @return An integer alpha value for the pointer (0..255) when touched - */ - public int getPointerAlphaOnTouch() { - return mPointerAlphaOnTouch; - } - - /** - * Sets the pointer angle. - * @param angle the angle of the pointer - */ - public void setPointerAngle(float angle) { - // Modulo 360 right now to avoid constant conversion - angle = ((360f + (angle % 360f)) % 360f); - if (angle == 0f) { - angle = SMALL_DEGREE_BIAS; - } - if (angle != mPointerAngle) { - mPointerAngle = angle; - recalculateAll(); - invalidate(); - } - } - - /** - * Gets the pointer angle. - * @return Angle for the pointer (0..360) - */ - public float getPointerAngle() { - return mPointerAngle; - } - - /** - * Sets the circle fill color. - * @param color the color of the circle fill - */ - public void setCircleFillColor(int color) { - mCircleFillColor = color; - mCircleFillPaint.setColor(mCircleFillColor); - invalidate(); - } - - /** - * Gets the circle fill color. - * @return An integer color value for the circle fill - */ - public int getCircleFillColor() { - return mCircleFillColor; - } - - /** - * Set the max of the CircularSeekBar. - * If the new max is less than the current progress, then the progress will be set to zero. - * If the progress is changed as a result, then any listener will receive a onProgressChanged event. - * @param max The new max for the CircularSeekBar. - */ - public void setMax(float max) { - if (max > 0) { // Check to make sure it's greater than zero - if (max <= mProgress) { - mProgress = 0; // If the new max is less than current progress, set progress to zero - if (mOnCircularSeekBarChangeListener != null) { - mOnCircularSeekBarChangeListener.onProgressChanged(this, mIsInNegativeHalf ? -mProgress : mProgress, false); - } - } - mMax = max; - - recalculateAll(); - invalidate(); - } - } - - /** - * Get the current max of the CircularSeekBar. - * @return Synchronized integer value of the max. - */ - public synchronized float getMax() { - return mMax; - } - - public RectF getPathCircle() { - return mCircleRectF; - } - -} diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBarV2.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt similarity index 99% rename from circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBarV2.kt rename to circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index 9fa2e753..4745eab3 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBarV2.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -27,7 +27,6 @@ */ package me.tankery.lib.circularseekbar -import android.annotation.SuppressLint import android.content.Context import android.content.res.TypedArray import android.graphics.* @@ -35,12 +34,11 @@ import android.graphics.Paint.Cap import android.os.Build import android.os.Bundle import android.os.Parcelable -import android.support.annotation.RequiresApi import android.util.AttributeSet import android.view.MotionEvent import android.view.View -class CircularSeekBarV2 @JvmOverloads constructor( +class CircularSeekBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, @@ -1148,13 +1146,13 @@ class CircularSeekBarV2 @JvmOverloads constructor( */ interface OnCircularSeekBarChangeListener { fun onProgressChanged( - circularSeekBar: CircularSeekBarV2?, + circularSeekBar: CircularSeekBar?, progress: Float, fromUser: Boolean ) - fun onStopTrackingTouch(seekBar: CircularSeekBarV2?) - fun onStartTrackingTouch(seekBar: CircularSeekBarV2?) + fun onStopTrackingTouch(seekBar: CircularSeekBar?) + fun onStartTrackingTouch(seekBar: CircularSeekBar?) } companion object { From d6f76731cd0beb04300d1138ba345086ae190549 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 11 May 2022 15:15:50 -0400 Subject: [PATCH 06/17] Removes Hungarian notation. Should now be backwards compatible with old getters and setters https://jakewharton.com/just-say-no-to-hungarian-notation/ --- .../lib/circularseekbar/CircularSeekBar.kt | 669 +++++++++--------- 1 file changed, 335 insertions(+), 334 deletions(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index 4745eab3..dd973fb0 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -52,17 +52,17 @@ class CircularSeekBar @JvmOverloads constructor( /** * `Paint` instance used to draw the inactive circle. */ - private val mCirclePaint: Paint = Paint() + private val circlePaint: Paint = Paint() /** * `Paint` instance used to draw the circle fill. */ - private val mCircleFillPaint: Paint = Paint() + private val circleFillPaint: Paint = Paint() /** * `Paint` instance used to draw the active circle (represents progress). */ - private val mCircleProgressPaint: Paint = Paint() + private val circleProgressPaint: Paint = Paint() /** * If progress glow is disabled, there is no glow from the progress bar when filled @@ -71,34 +71,34 @@ class CircularSeekBar @JvmOverloads constructor( * accelerate disabled. (Checkout this doc for details of hardware accelerate: * https://developer.android.com/guide/topics/graphics/hardware-accel) */ - private var mDisableProgressGlow = false + private var disableProgressGlow = false /** * `Paint` instance used to draw the glow from the active circle. */ - private val mCircleProgressGlowPaint: Paint = Paint() + private val circleProgressGlowPaint: Paint = Paint() /** * `Paint` instance used to draw the center of the pointer. * Note: This is broken on 4.0+, as BlurMasks do not work with hardware acceleration. */ - private val mPointerPaint: Paint = Paint() + private val pointerPaint: Paint = Paint() /** * `Paint` instance used to draw the halo of the pointer. * Note: The halo is the part that changes transparency. */ - private val mPointerHaloPaint: Paint = Paint() + private val pointerHaloPaint: Paint = Paint() /** * `Paint` instance used to draw the border of the pointer, outside of the halo. */ - private val mPointerHaloBorderPaint: Paint = Paint() + private val pointerHaloBorderPaint: Paint = Paint() /** * The style of the circle, can be butt, round or square. */ - var mCircleStyle: Cap = Cap.ROUND + var circleStyle: Cap = Cap.ROUND set(style) { field = style initPaints() @@ -109,14 +109,14 @@ class CircularSeekBar @JvmOverloads constructor( /** * current in negative half cycle. */ - private var mIsInNegativeHalf = false + private var isInNegativeHalf = false /** * The width of the circle (in pixels). * Sets the circle stroke width. * @param width the width of the circle */ - var mCircleStrokeWidth = 0f + var circleStrokeWidth = 0f set(width) { field = width initPaints() @@ -127,17 +127,17 @@ class CircularSeekBar @JvmOverloads constructor( /** * The X radius of the circle (in pixels). */ - private var mCircleXRadius = 0f + private var circleXRadius = 0f /** * The Y radius of the circle (in pixels). */ - private var mCircleYRadius = 0f + private var circleYRadius = 0f /** * If disable pointer, we can't seek the progress. */ - private var mDisablePointer = false + private var disablePointer = false /** * The radius of the pointer (in pixels). @@ -145,7 +145,7 @@ class CircularSeekBar @JvmOverloads constructor( * Sets the pointer pointer stroke width. * @param width the width of the pointer */ - var mPointerStrokeWidth = 0f + var pointerStrokeWidth = 0f set(width) { field = width initPaints() @@ -156,12 +156,12 @@ class CircularSeekBar @JvmOverloads constructor( /** * The width of the pointer halo (in pixels). */ - private var mPointerHaloWidth = 0f + private var pointerHaloWidth = 0f /** * The width of the pointer halo border (in pixels). */ - private var mPointerHaloBorderWidth = 0f + private var pointerHaloBorderWidth = 0f /** * Angle of the pointer arc. @@ -171,7 +171,7 @@ class CircularSeekBar @JvmOverloads constructor( * Gets the pointer angle. * @return Angle for the pointer (0..360) */ - var mPointerAngle = 0f + var pointerAngle = 0f set(angle) { // Modulo 360 right now to avoid constant conversion var normalizedAngle = (360f + angle % 360f) % 360f @@ -190,12 +190,12 @@ class CircularSeekBar @JvmOverloads constructor( * Note: If mStartAngle and mEndAngle are set to the same angle, 0.1 is subtracted * from the mEndAngle to make the circle function properly. */ - var mStartAngle = 0f + var startAngle = 0f set(angle) { field = angle - if (angle % 360f == mEndAngle % 360f) { + if (angle % 360f == endAngle % 360f) { //mStartAngle = mStartAngle + 1f; - mEndAngle -= SMALL_DEGREE_BIAS + endAngle -= SMALL_DEGREE_BIAS } recalculateAll() invalidate() @@ -206,9 +206,9 @@ class CircularSeekBar @JvmOverloads constructor( * Note: If mStartAngle and mEndAngle are set to the same angle, 0.1 is subtracted * from the mEndAngle to make the circle function properly. */ - var mEndAngle = 0f + var endAngle = 0f set(angle) { - field = if (mStartAngle % 360f == mEndAngle % 360f) { + field = if (startAngle % 360f == endAngle % 360f) { //mStartAngle = mStartAngle + 1f; angle - SMALL_DEGREE_BIAS } else { @@ -232,10 +232,10 @@ class CircularSeekBar @JvmOverloads constructor( * @param color the color of the pointer * @return An integer color value for the pointer */ - var mPointerColor = DEFAULT_POINTER_COLOR + var pointerColor = DEFAULT_POINTER_COLOR set(color) { field = color - mPointerPaint.color = color + pointerPaint.color = color invalidate() } @@ -249,17 +249,17 @@ class CircularSeekBar @JvmOverloads constructor( * @param color the color of the pointer halo * @return An integer color value for the pointer halo */ - var mPointerHaloColor = DEFAULT_POINTER_HALO_COLOR + var pointerHaloColor = DEFAULT_POINTER_HALO_COLOR set(color) { field = color - mPointerHaloPaint.color = field + pointerHaloPaint.color = field invalidate() } /** * Holds the color value for `mPointerHaloPaint` before the `Paint` instance is created. */ - private var mPointerHaloColorOnTouch = DEFAULT_POINTER_HALO_COLOR_ONTOUCH + private var pointerHaloColorOnTouch = DEFAULT_POINTER_HALO_COLOR_ONTOUCH /** * Holds the color value for `mCirclePaint` before the `Paint` instance is created. @@ -270,10 +270,10 @@ class CircularSeekBar @JvmOverloads constructor( * @param color the color of the circle * @return An integer color value for the circle */ - var mCircleColor = DEFAULT_CIRCLE_COLOR + var circleColor = DEFAULT_CIRCLE_COLOR set(color) { field = color - mCirclePaint.color = color + circlePaint.color = color invalidate() } @@ -286,10 +286,10 @@ class CircularSeekBar @JvmOverloads constructor( * @param color the color of the circle fill * @return An integer color value for the circle fill */ - var mCircleFillColor = DEFAULT_CIRCLE_FILL_COLOR + var circleFillColor = DEFAULT_CIRCLE_FILL_COLOR set(color) { field = color - mCircleFillPaint.color = color + circleFillPaint.color = color invalidate() } @@ -302,10 +302,10 @@ class CircularSeekBar @JvmOverloads constructor( * @param color the color of the circle progress * @return An integer color value for the circle progress */ - var mCircleProgressColor = DEFAULT_CIRCLE_PROGRESS_COLOR + var circleProgressColor = DEFAULT_CIRCLE_PROGRESS_COLOR set(color) { field = color - mCircleProgressPaint.color = color + circleProgressPaint.color = color invalidate() } @@ -318,11 +318,11 @@ class CircularSeekBar @JvmOverloads constructor( * @param alpha the alpha of the pointer * @return An integer alpha value for the pointer (0..255) */ - var mPointerAlpha = DEFAULT_POINTER_ALPHA + var pointerAlpha = DEFAULT_POINTER_ALPHA set(alpha) { if (alpha in 0..255) { field = alpha - mPointerHaloPaint.alpha = alpha + pointerHaloPaint.alpha = alpha invalidate() } } @@ -336,7 +336,7 @@ class CircularSeekBar @JvmOverloads constructor( * @param alpha the alpha of the pointer (0..255) when touched * @return An integer alpha value for the pointer (0..255) when touched */ - var mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH + var pointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH set(alpha) { if (alpha in 0..255) { field = alpha @@ -347,27 +347,27 @@ class CircularSeekBar @JvmOverloads constructor( * Distance (in degrees) that the the circle/semi-circle makes up. * This amount represents the max of the circle in degrees. */ - private var mTotalCircleDegrees = 0f + private var totalCircleDegrees = 0f /** * Distance (in degrees) that the current progress makes up in the circle. */ - private var mProgressDegrees = 0f + private var progressDegrees = 0f /** * `Path` used to draw the circle/semi-circle. */ - private val mCirclePath: Path = Path() + private val circlePath: Path = Path() /** * `Path` used to draw the progress on the circle. */ - private val mCircleProgressPath: Path = Path() + private val circleProgressPath: Path = Path() /** * `Path` used to draw the pointer arc on the circle. */ - private val mCirclePointerPath: Path = Path() + private val circlePointerPath: Path = Path() /** * Max value that this CircularSeekBar is representing. @@ -381,14 +381,14 @@ class CircularSeekBar @JvmOverloads constructor( * @return Synchronized integer value of the max. */ @get:Synchronized - var mMax = 0f + var max = 0f set(max) { if (max > 0) { - if (max <= mProgress) { - mProgress = 0f // If the new max is less than current progress, set progress to zero - mOnCircularSeekBarChangeListener?.onProgressChanged( + if (max <= progressActual) { + progressActual = 0f // If the new max is less than current progress, set progress to zero + onCircularSeekBarChangeListener?.onProgressChanged( this, - if (mIsInNegativeHalf) -mProgress else mProgress, + if (isInNegativeHalf) -progressActual else progressActual, false ) } @@ -401,7 +401,42 @@ class CircularSeekBar @JvmOverloads constructor( /** * Progress value that this CircularSeekBar is representing. */ - private var mProgress = 0f + private var progressActual = 0f + + /** + * Get the progress of the CircularSeekBar. + * + * Set the progress of the CircularSeekBar. + * If the progress is the same, then any listener will not receive a onProgressChanged event. + * + * @param progress The progress to set the CircularSeekBar to. + * @return The progress of the CircularSeekBar. + */ + var progress: Float + get() { + val progress = max * progressDegrees / totalCircleDegrees + return if (isInNegativeHalf) -progress else progress + } + set(progress) { + if (progressActual != progress) { + if (isNegativeEnabled) { + if (progress < 0) { + progressActual = -progress + isInNegativeHalf = true + } else { + progressActual = progress + isInNegativeHalf = false + } + } else { + progressActual = progress + } + + onCircularSeekBarChangeListener?.onProgressChanged(this, progress, false) + + recalculateAll() + invalidate() + } + } /** * Used for enabling/disabling the negative progress bar. @@ -412,20 +447,20 @@ class CircularSeekBar @JvmOverloads constructor( * If true, then the user can specify the X and Y radii. * If false, then the View itself determines the size of the CircularSeekBar. */ - private var mCustomRadii = false + private var customRadii = false /** * Maintain a perfect circle (equal x and y radius), regardless of view or custom attributes. * The smaller of the two radii will always be used in this case. * The default is to be a circle and not an ellipse, due to the behavior of the ellipse. */ - private var mMaintainEqualCircle = false + private var maintainEqualCircle = false /** * Once a user has touched the circle, this determines if moving outside the circle is able * to change the position of the pointer (and in turn, the progress). */ - private var mMoveOutsideCircle = false + private var moveOutsideCircle = false /** * Used for enabling/disabling the lock option for easier hitting of the 0 progress mark. @@ -436,52 +471,52 @@ class CircularSeekBar @JvmOverloads constructor( * Used for when the user moves beyond the start of the circle when moving counter clockwise. * Makes it easier to hit the 0 progress mark. */ - private var mLockAtStart = true + private var lockAtStart = true /** * Used for when the user moves beyond the end of the circle when moving clockwise. * Makes it easier to hit the 100% (max) progress mark. */ - private var mLockAtEnd = false + private var lockAtEnd = false /** * If progress is zero, hide the progress bar. */ - private var mHideProgressWhenEmpty = false + private var hideProgressWhenEmpty = false /** * When the user is touching the circle on ACTION_DOWN, this is set to true. * Used when touching the CircularSeekBar. */ - private var mUserIsMovingPointer = false + private var userIsMovingPointer = false /** * The width of the circle used in the `RectF` that is used to draw it. * Based on either the View width or the custom X radius. */ - private var mCircleWidth = 0f + private var circleWidth = 0f /** * The height of the circle used in the `RectF` that is used to draw it. * Based on either the View width or the custom Y radius. */ - private var mCircleHeight = 0f + private var circleHeight = 0f /** * Represents the progress mark on the circle, in geometric degrees. * This is not provided by the user; it is calculated; */ - private var mPointerPosition = 0f + private var pointerPosition = 0f /** * Pointer position in terms of X and Y coordinates. */ - private val mPointerPositionXY = FloatArray(2) + private val pointerPositionXY = FloatArray(2) /** * Listener. */ - private var mOnCircularSeekBarChangeListener: OnCircularSeekBarChangeListener? = null + private var onCircularSeekBarChangeListener: OnCircularSeekBarChangeListener? = null init { val attrArray = context.theme.obtainStyledAttributes(attrs, R.styleable.cs_CircularSeekBar, defStyleAttr, defStyleRes) @@ -496,75 +531,75 @@ class CircularSeekBar @JvmOverloads constructor( * @param attrArray TypedArray containing the attributes. */ private fun initAttributes(attrArray: TypedArray) { - mCircleXRadius = attrArray.getDimension( + circleXRadius = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_circle_x_radius, DEFAULT_CIRCLE_X_RADIUS ) - mCircleYRadius = attrArray.getDimension( + circleYRadius = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_circle_y_radius, DEFAULT_CIRCLE_Y_RADIUS ) - mPointerStrokeWidth = attrArray.getDimension( + pointerStrokeWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_pointer_stroke_width, DEFAULT_POINTER_STROKE_WIDTH ) - mPointerHaloWidth = attrArray.getDimension( + pointerHaloWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_pointer_halo_width, DEFAULT_POINTER_HALO_WIDTH ) - mPointerHaloBorderWidth = attrArray.getDimension( + pointerHaloBorderWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_pointer_halo_border_width, DEFAULT_POINTER_HALO_BORDER_WIDTH ) - mCircleStrokeWidth = attrArray.getDimension( + circleStrokeWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_circle_stroke_width, DEFAULT_CIRCLE_STROKE_WIDTH ) - val circleStyle = + val circleStyleAttribute = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_circle_style, DEFAULT_CIRCLE_STYLE) - mCircleStyle = Cap.values()[circleStyle] - mPointerColor = attrArray.getColor( + circleStyle = Cap.values()[circleStyleAttribute] + pointerColor = attrArray.getColor( R.styleable.cs_CircularSeekBar_cs_pointer_color, DEFAULT_POINTER_COLOR ) - mPointerHaloColor = attrArray.getColor( + pointerHaloColor = attrArray.getColor( R.styleable.cs_CircularSeekBar_cs_pointer_halo_color, DEFAULT_POINTER_HALO_COLOR ) - mPointerHaloColorOnTouch = attrArray.getColor( + pointerHaloColorOnTouch = attrArray.getColor( R.styleable.cs_CircularSeekBar_cs_pointer_halo_color_ontouch, DEFAULT_POINTER_HALO_COLOR_ONTOUCH ) - mCircleColor = + circleColor = attrArray.getColor(R.styleable.cs_CircularSeekBar_cs_circle_color, DEFAULT_CIRCLE_COLOR) - mCircleProgressColor = attrArray.getColor( + circleProgressColor = attrArray.getColor( R.styleable.cs_CircularSeekBar_cs_circle_progress_color, DEFAULT_CIRCLE_PROGRESS_COLOR ) - mCircleFillColor = attrArray.getColor( + circleFillColor = attrArray.getColor( R.styleable.cs_CircularSeekBar_cs_circle_fill, DEFAULT_CIRCLE_FILL_COLOR ) - mPointerAlpha = Color.alpha(mPointerHaloColor) - mPointerAlphaOnTouch = attrArray.getInt( + pointerAlpha = Color.alpha(pointerHaloColor) + pointerAlphaOnTouch = attrArray.getInt( R.styleable.cs_CircularSeekBar_cs_pointer_alpha_ontouch, DEFAULT_POINTER_ALPHA_ONTOUCH ) - if (mPointerAlphaOnTouch > 255 || mPointerAlphaOnTouch < 0) { - mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH + if (pointerAlphaOnTouch > 255 || pointerAlphaOnTouch < 0) { + pointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH } - mMax = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_max, DEFAULT_MAX).toFloat() - mProgress = + max = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_max, DEFAULT_MAX).toFloat() + progressActual = attrArray.getInt(R.styleable.cs_CircularSeekBar_cs_progress, DEFAULT_PROGRESS).toFloat() - mCustomRadii = attrArray.getBoolean( + customRadii = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_use_custom_radii, DEFAULT_USE_CUSTOM_RADII ) - mMaintainEqualCircle = attrArray.getBoolean( + maintainEqualCircle = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_maintain_equal_circle, DEFAULT_MAINTAIN_EQUAL_CIRCLE ) - mMoveOutsideCircle = attrArray.getBoolean( + moveOutsideCircle = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_move_outside_circle, DEFAULT_MOVE_OUTSIDE_CIRCLE ) @@ -572,7 +607,7 @@ class CircularSeekBar @JvmOverloads constructor( R.styleable.cs_CircularSeekBar_cs_lock_enabled, DEFAULT_LOCK_ENABLED ) - mDisablePointer = attrArray.getBoolean( + disablePointer = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_disable_pointer, DEFAULT_DISABLE_POINTER ) @@ -580,47 +615,47 @@ class CircularSeekBar @JvmOverloads constructor( R.styleable.cs_CircularSeekBar_cs_negative_enabled, DEFAULT_NEGATIVE_ENABLED ) - mIsInNegativeHalf = false - mDisableProgressGlow = attrArray.getBoolean( + isInNegativeHalf = false + disableProgressGlow = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_disable_progress_glow, DEFAULT_DISABLE_PROGRESS_GLOW ) - mHideProgressWhenEmpty = attrArray.getBoolean( + hideProgressWhenEmpty = attrArray.getBoolean( R.styleable.cs_CircularSeekBar_cs_hide_progress_when_empty, DEFAULT_CS_HIDE_PROGRESS_WHEN_EMPTY ) // Modulo 360 right now to avoid constant conversion - mStartAngle = (360f + attrArray.getFloat( + startAngle = (360f + attrArray.getFloat( R.styleable.cs_CircularSeekBar_cs_start_angle, DEFAULT_START_ANGLE ) % 360f) % 360f - mEndAngle = (360f + attrArray.getFloat( + endAngle = (360f + attrArray.getFloat( R.styleable.cs_CircularSeekBar_cs_end_angle, DEFAULT_END_ANGLE ) % 360f) % 360f // Disable negative progress if is semi-oval. - if (mStartAngle != mEndAngle) { + if (startAngle != endAngle) { isNegativeEnabled = false } - if (mStartAngle % 360f == mEndAngle % 360f) { + if (startAngle % 360f == endAngle % 360f) { //mStartAngle = mStartAngle + 1f; - mEndAngle = mEndAngle - SMALL_DEGREE_BIAS + endAngle = endAngle - SMALL_DEGREE_BIAS } // Modulo 360 right now to avoid constant conversion - mPointerAngle = (360f + attrArray.getFloat( + pointerAngle = (360f + attrArray.getFloat( R.styleable.cs_CircularSeekBar_cs_pointer_angle, DEFAULT_POINTER_ANGLE ) % 360f) % 360f - if (mPointerAngle == 0f) { - mPointerAngle = SMALL_DEGREE_BIAS + if (pointerAngle == 0f) { + pointerAngle = SMALL_DEGREE_BIAS } - if (mDisablePointer) { - mPointerStrokeWidth = 0f - mPointerHaloWidth = 0f - mPointerHaloBorderWidth = 0f + if (disablePointer) { + pointerStrokeWidth = 0f + pointerHaloWidth = 0f + pointerHaloBorderWidth = 0f } } @@ -628,50 +663,50 @@ class CircularSeekBar @JvmOverloads constructor( * Initializes the `Paint` objects with the appropriate styles. */ private fun initPaints() { - mCirclePaint.isAntiAlias = true - mCirclePaint.isDither = true - mCirclePaint.color = mCircleColor - mCirclePaint.strokeWidth = mCircleStrokeWidth - mCirclePaint.style = Paint.Style.STROKE - mCirclePaint.strokeJoin = Paint.Join.ROUND - mCirclePaint.strokeCap = mCircleStyle - - mCircleFillPaint.isAntiAlias = true - mCircleFillPaint.isDither = true - mCircleFillPaint.color = mCircleFillColor - mCircleFillPaint.style = Paint.Style.FILL - - mCircleProgressPaint.isAntiAlias = true - mCircleProgressPaint.isDither = true - mCircleProgressPaint.color = mCircleProgressColor - mCircleProgressPaint.strokeWidth = mCircleStrokeWidth - mCircleProgressPaint.style = Paint.Style.STROKE - mCircleProgressPaint.strokeJoin = Paint.Join.ROUND - mCircleProgressPaint.strokeCap = mCircleStyle - if (!mDisableProgressGlow) { - mCircleProgressGlowPaint.set(mCircleProgressPaint) - mCircleProgressGlowPaint.maskFilter = BlurMaskFilter( + circlePaint.isAntiAlias = true + circlePaint.isDither = true + circlePaint.color = circleColor + circlePaint.strokeWidth = circleStrokeWidth + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeJoin = Paint.Join.ROUND + circlePaint.strokeCap = circleStyle + + circleFillPaint.isAntiAlias = true + circleFillPaint.isDither = true + circleFillPaint.color = circleFillColor + circleFillPaint.style = Paint.Style.FILL + + circleProgressPaint.isAntiAlias = true + circleProgressPaint.isDither = true + circleProgressPaint.color = circleProgressColor + circleProgressPaint.strokeWidth = circleStrokeWidth + circleProgressPaint.style = Paint.Style.STROKE + circleProgressPaint.strokeJoin = Paint.Join.ROUND + circleProgressPaint.strokeCap = circleStyle + if (!disableProgressGlow) { + circleProgressGlowPaint.set(circleProgressPaint) + circleProgressGlowPaint.maskFilter = BlurMaskFilter( PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE, BlurMaskFilter.Blur.NORMAL ) } - mPointerPaint.isAntiAlias = true - mPointerPaint.isDither = true - mPointerPaint.color = mPointerColor - mPointerPaint.strokeWidth = mPointerStrokeWidth - mPointerPaint.style = Paint.Style.STROKE - mPointerPaint.strokeJoin = Paint.Join.ROUND - mPointerPaint.strokeCap = mCircleStyle - - mPointerHaloPaint.set(mPointerPaint) - mPointerHaloPaint.color = mPointerHaloColor - mPointerHaloPaint.alpha = mPointerAlpha - mPointerHaloPaint.strokeWidth = mPointerStrokeWidth + mPointerHaloWidth * 2f - - mPointerHaloBorderPaint.set(mPointerPaint) - mPointerHaloBorderPaint.strokeWidth = mPointerHaloBorderWidth - mPointerHaloBorderPaint.style = Paint.Style.STROKE + pointerPaint.isAntiAlias = true + pointerPaint.isDither = true + pointerPaint.color = pointerColor + pointerPaint.strokeWidth = pointerStrokeWidth + pointerPaint.style = Paint.Style.STROKE + pointerPaint.strokeJoin = Paint.Join.ROUND + pointerPaint.strokeCap = circleStyle + + pointerHaloPaint.set(pointerPaint) + pointerHaloPaint.color = pointerHaloColor + pointerHaloPaint.alpha = pointerAlpha + pointerHaloPaint.strokeWidth = pointerStrokeWidth + pointerHaloWidth * 2f + + pointerHaloBorderPaint.set(pointerPaint) + pointerHaloBorderPaint.strokeWidth = pointerHaloBorderWidth + pointerHaloBorderPaint.style = Paint.Style.STROKE } /** @@ -679,10 +714,10 @@ class CircularSeekBar @JvmOverloads constructor( * to this value. */ private fun calculateTotalDegrees() { - mTotalCircleDegrees = - (360f - (mStartAngle - mEndAngle)) % 360f // Length of the entire circle/arc - if (mTotalCircleDegrees <= 0f) { - mTotalCircleDegrees = 360f + totalCircleDegrees = + (360f - (startAngle - endAngle)) % 360f // Length of the entire circle/arc + if (totalCircleDegrees <= 0f) { + totalCircleDegrees = 360f } } @@ -691,10 +726,10 @@ class CircularSeekBar @JvmOverloads constructor( * Sets mProgressDegrees to that value. */ private fun calculateProgressDegrees() { - mProgressDegrees = - if (mIsInNegativeHalf) mStartAngle - mPointerPosition else mPointerPosition - mStartAngle // Verified - mProgressDegrees = - if (mProgressDegrees < 0) 360f + mProgressDegrees else mProgressDegrees // Verified + progressDegrees = + if (isInNegativeHalf) startAngle - pointerPosition else pointerPosition - startAngle // Verified + progressDegrees = + if (progressDegrees < 0) 360f + progressDegrees else progressDegrees // Verified } /** @@ -702,19 +737,19 @@ class CircularSeekBar @JvmOverloads constructor( * Sets mPointerPosition to that value. */ private fun calculatePointerPosition() { - val progressPercent = mProgress / mMax - val progressDegree = progressPercent * mTotalCircleDegrees - mPointerPosition = mStartAngle + if (mIsInNegativeHalf) -progressDegree else progressDegree - mPointerPosition = - (if (mPointerPosition < 0) 360f + mPointerPosition else mPointerPosition) % 360f + val progressPercent = progressActual / max + val progressDegree = progressPercent * totalCircleDegrees + pointerPosition = startAngle + if (isInNegativeHalf) -progressDegree else progressDegree + pointerPosition = + (if (pointerPosition < 0) 360f + pointerPosition else pointerPosition) % 360f } private fun calculatePointerXYPosition() { - var pm = PathMeasure(mCircleProgressPath, false) - val returnValue = pm.getPosTan(pm.length, mPointerPositionXY, null) + var pm = PathMeasure(circleProgressPath, false) + val returnValue = pm.getPosTan(pm.length, pointerPositionXY, null) if (!returnValue) { - pm = PathMeasure(mCirclePath, false) - pm.getPosTan(0f, mPointerPositionXY, null) + pm = PathMeasure(circlePath, false) + pm.getPosTan(0f, pointerPositionXY, null) } } @@ -722,36 +757,36 @@ class CircularSeekBar @JvmOverloads constructor( * Reset the `Path` objects with the appropriate values. */ private fun resetPaths() { - if (mIsInNegativeHalf) { - mCirclePath.reset() - mCirclePath.addArc(pathCircle, mStartAngle - mTotalCircleDegrees, mTotalCircleDegrees) + if (isInNegativeHalf) { + circlePath.reset() + circlePath.addArc(pathCircle, startAngle - totalCircleDegrees, totalCircleDegrees) // beside progress path it self, we also draw a extend arc to math the pointer arc. - val extendStart = mStartAngle - mProgressDegrees - mPointerAngle / 2.0f - var extendDegrees = mProgressDegrees + mPointerAngle + val extendStart = startAngle - progressDegrees - pointerAngle / 2.0f + var extendDegrees = progressDegrees + pointerAngle if (extendDegrees >= 360f) { extendDegrees = 360f - SMALL_DEGREE_BIAS } - mCircleProgressPath.reset() - mCircleProgressPath.addArc(pathCircle, extendStart, extendDegrees) - val pointerStart = mPointerPosition - mPointerAngle / 2.0f - mCirclePointerPath.reset() - mCirclePointerPath.addArc(pathCircle, pointerStart, mPointerAngle) + circleProgressPath.reset() + circleProgressPath.addArc(pathCircle, extendStart, extendDegrees) + val pointerStart = pointerPosition - pointerAngle / 2.0f + circlePointerPath.reset() + circlePointerPath.addArc(pathCircle, pointerStart, pointerAngle) } else { - mCirclePath.reset() - mCirclePath.addArc(pathCircle, mStartAngle, mTotalCircleDegrees) + circlePath.reset() + circlePath.addArc(pathCircle, startAngle, totalCircleDegrees) // beside progress path it self, we also draw a extend arc to math the pointer arc. - val extendStart = mStartAngle - mPointerAngle / 2.0f - var extendDegrees = mProgressDegrees + mPointerAngle + val extendStart = startAngle - pointerAngle / 2.0f + var extendDegrees = progressDegrees + pointerAngle if (extendDegrees >= 360f) { extendDegrees = 360f - SMALL_DEGREE_BIAS } - mCircleProgressPath.reset() - mCircleProgressPath.addArc(pathCircle, extendStart, extendDegrees) - val pointerStart = mPointerPosition - mPointerAngle / 2.0f - mCirclePointerPath.reset() - mCirclePointerPath.addArc(pathCircle, pointerStart, mPointerAngle) + circleProgressPath.reset() + circleProgressPath.addArc(pathCircle, extendStart, extendDegrees) + val pointerStart = pointerPosition - pointerAngle / 2.0f + circlePointerPath.reset() + circlePointerPath.addArc(pathCircle, pointerStart, pointerAngle) } } @@ -759,31 +794,31 @@ class CircularSeekBar @JvmOverloads constructor( * Initialize the `RectF` objects with the appropriate values. */ private fun resetRects() { - pathCircle[-mCircleWidth, -mCircleHeight, mCircleWidth] = mCircleHeight + pathCircle[-circleWidth, -circleHeight, circleWidth] = circleHeight } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.translate(width / 2f, height / 2f) - canvas.drawPath(mCirclePath, mCircleFillPaint) - canvas.drawPath(mCirclePath, mCirclePaint) + canvas.drawPath(circlePath, circleFillPaint) + canvas.drawPath(circlePath, circlePaint) val ableToGoNegative = - isNegativeEnabled && Math.abs(mTotalCircleDegrees - 360f) < SMALL_DEGREE_BIAS * 2 + isNegativeEnabled && Math.abs(totalCircleDegrees - 360f) < SMALL_DEGREE_BIAS * 2 // Hide progress bar when progress is 0 // Also make sure we still draw progress when has pointer or able to go negative - val shouldHideProgress = mHideProgressWhenEmpty && mProgressDegrees == 0f && - mDisablePointer && !ableToGoNegative + val shouldHideProgress = hideProgressWhenEmpty && progressDegrees == 0f && + disablePointer && !ableToGoNegative if (!shouldHideProgress) { - if (!mDisableProgressGlow) { - canvas.drawPath(mCircleProgressPath, mCircleProgressGlowPaint) + if (!disableProgressGlow) { + canvas.drawPath(circleProgressPath, circleProgressGlowPaint) } - canvas.drawPath(mCircleProgressPath, mCircleProgressPaint) + canvas.drawPath(circleProgressPath, circleProgressPaint) } - if (!mDisablePointer) { - if (mUserIsMovingPointer) { - canvas.drawPath(mCirclePointerPath, mPointerHaloPaint) + if (!disablePointer) { + if (userIsMovingPointer) { + canvas.drawPath(circlePointerPath, pointerHaloPaint) } - canvas.drawPath(mCirclePointerPath, mPointerPaint) + canvas.drawPath(circlePointerPath, pointerPaint) // TODO, find a good way to draw halo border. // if (mUserIsMovingPointer) { // canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], @@ -792,45 +827,11 @@ class CircularSeekBar @JvmOverloads constructor( // } } } - /** - * Get the progress of the CircularSeekBar. - * @return The progress of the CircularSeekBar. - */ - /** - * Set the progress of the CircularSeekBar. - * If the progress is the same, then any listener will not receive a onProgressChanged event. - * @param progress The progress to set the CircularSeekBar to. - */ - var progress: Float - get() { - val progress = mMax * mProgressDegrees / mTotalCircleDegrees - return if (mIsInNegativeHalf) -progress else progress - } - set(progress) { - if (mProgress != progress) { - if (isNegativeEnabled) { - if (progress < 0) { - mProgress = -progress - mIsInNegativeHalf = true - } else { - mProgress = progress - mIsInNegativeHalf = false - } - } else { - mProgress = progress - } - - mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, false) - - recalculateAll() - invalidate() - } - } private fun setProgressBasedOnAngle(angle: Float) { - mPointerPosition = angle + pointerPosition = angle calculateProgressDegrees() - mProgress = mMax * mProgressDegrees / mTotalCircleDegrees + progressActual = max * progressDegrees / totalCircleDegrees } private fun recalculateAll() { @@ -847,7 +848,7 @@ class CircularSeekBar @JvmOverloads constructor( var width = getDefaultSize(suggestedMinimumWidth, widthMeasureSpec) if (height == 0) height = width if (width == 0) width = height - if (mMaintainEqualCircle) { + if (maintainEqualCircle) { val min = Math.min(width, height) setMeasuredDimension(min, min) } else { @@ -855,37 +856,37 @@ class CircularSeekBar @JvmOverloads constructor( } val isHardwareAccelerated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && isHardwareAccelerated && layerType != LAYER_TYPE_SOFTWARE - val hasGlowEffect = !mDisableProgressGlow && !isHardwareAccelerated + val hasGlowEffect = !disableProgressGlow && !isHardwareAccelerated // Set the circle width and height based on the view for the moment val padding = Math.max( - mCircleStrokeWidth / 2f, - mPointerStrokeWidth / 2 + mPointerHaloWidth + mPointerHaloBorderWidth + circleStrokeWidth / 2f, + pointerStrokeWidth / 2 + pointerHaloWidth + pointerHaloBorderWidth ) + if (hasGlowEffect) PROGRESS_GLOW_RADIUS_DP * DPTOPX_SCALE else 0f - mCircleHeight = height / 2f - padding - mCircleWidth = width / 2f - padding + circleHeight = height / 2f - padding + circleWidth = width / 2f - padding // If it is not set to use custom - if (mCustomRadii) { + if (customRadii) { // Check to make sure the custom radii are not out of the view. If they are, just use the view values - if (mCircleYRadius - padding < mCircleHeight) { - mCircleHeight = mCircleYRadius - padding + if (circleYRadius - padding < circleHeight) { + circleHeight = circleYRadius - padding } - if (mCircleXRadius - padding < mCircleWidth) { - mCircleWidth = mCircleXRadius - padding + if (circleXRadius - padding < circleWidth) { + circleWidth = circleXRadius - padding } } - if (mMaintainEqualCircle) { // Applies regardless of how the values were determined - val min = Math.min(mCircleHeight, mCircleWidth) - mCircleHeight = min - mCircleWidth = min + if (maintainEqualCircle) { // Applies regardless of how the values were determined + val min = Math.min(circleHeight, circleWidth) + circleHeight = min + circleWidth = min } recalculateAll() } override fun onTouchEvent(event: MotionEvent): Boolean { - if (mDisablePointer || !isEnabled) return false + if (disablePointer || !isEnabled) return false // Convert coordinates to our internal coordinate system val x = event.x - width / 2 @@ -903,24 +904,24 @@ class CircularSeekBar @JvmOverloads constructor( MIN_TOUCH_TARGET_DP * DPTOPX_SCALE // Convert minimum touch target into px var additionalRadius: Float // Either uses the minimumTouchTarget size or larger if the ring/pointer is larger additionalRadius = - if (mCircleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the minimumTouchTarget + if (circleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the minimumTouchTarget minimumTouchTarget / 2 } else { - mCircleStrokeWidth / 2 // Otherwise use the width + circleStrokeWidth / 2 // Otherwise use the width } val outerRadius = Math.max( - mCircleHeight, - mCircleWidth + circleHeight, + circleWidth ) + additionalRadius // Max outer radius of the circle, including the minimumTouchTarget or wheel width val innerRadius = Math.min( - mCircleHeight, - mCircleWidth + circleHeight, + circleWidth ) - additionalRadius // Min inner radius of the circle, including the minimumTouchTarget or wheel width additionalRadius = - if (mPointerStrokeWidth < minimumTouchTarget / 2) { // If the pointer radius is less than the minimumTouchTarget, use the minimumTouchTarget + if (pointerStrokeWidth < minimumTouchTarget / 2) { // If the pointer radius is less than the minimumTouchTarget, use the minimumTouchTarget minimumTouchTarget / 2 } else { - mPointerStrokeWidth // Otherwise use the radius + pointerStrokeWidth // Otherwise use the radius } var touchAngle: Float touchAngle = @@ -964,23 +965,23 @@ class CircularSeekBar @JvmOverloads constructor( Used when touching the CircularSeekBar. */ val ccwDistanceFromPointer: Float - cwDistanceFromStart = touchAngle - mStartAngle // Verified + cwDistanceFromStart = touchAngle - startAngle // Verified cwDistanceFromStart = if (cwDistanceFromStart < 0) 360f + cwDistanceFromStart else cwDistanceFromStart // Verified ccwDistanceFromStart = 360f - cwDistanceFromStart // Verified - cwDistanceFromEnd = touchAngle - mEndAngle // Verified + cwDistanceFromEnd = touchAngle - endAngle // Verified cwDistanceFromEnd = if (cwDistanceFromEnd < 0) 360f + cwDistanceFromEnd else cwDistanceFromEnd // Verified ccwDistanceFromEnd = 360f - cwDistanceFromEnd // Verified when (event.action) { MotionEvent.ACTION_DOWN -> { // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched - val pointerRadiusDegrees = (mPointerStrokeWidth * 180 / (Math.PI * Math.max( - mCircleHeight, - mCircleWidth + val pointerRadiusDegrees = (pointerStrokeWidth * 180 / (Math.PI * Math.max( + circleHeight, + circleWidth ))).toFloat() - val pointerDegrees = Math.max(pointerRadiusDegrees, mPointerAngle / 2f) - cwDistanceFromPointer = touchAngle - mPointerPosition + val pointerDegrees = Math.max(pointerRadiusDegrees, pointerAngle / 2f) + cwDistanceFromPointer = touchAngle - pointerPosition cwDistanceFromPointer = if (cwDistanceFromPointer < 0) 360f + cwDistanceFromPointer else cwDistanceFromPointer ccwDistanceFromPointer = 360f - cwDistanceFromPointer @@ -988,97 +989,97 @@ class CircularSeekBar @JvmOverloads constructor( if (touchEventRadius >= innerRadius && touchEventRadius <= outerRadius && (cwDistanceFromPointer <= pointerDegrees || ccwDistanceFromPointer <= pointerDegrees) ) { - setProgressBasedOnAngle(mPointerPosition) - mPointerHaloPaint.alpha = mPointerAlphaOnTouch - mPointerHaloPaint.color = mPointerHaloColorOnTouch + setProgressBasedOnAngle(pointerPosition) + pointerHaloPaint.alpha = pointerAlphaOnTouch + pointerHaloPaint.color = pointerHaloColorOnTouch recalculateAll() invalidate() - mOnCircularSeekBarChangeListener?.onStartTrackingTouch(this) - mUserIsMovingPointer = true - mLockAtEnd = false - mLockAtStart = false - } else if (cwDistanceFromStart > mTotalCircleDegrees) { // If the user is touching outside of the start AND end - mUserIsMovingPointer = false + onCircularSeekBarChangeListener?.onStartTrackingTouch(this) + userIsMovingPointer = true + lockAtEnd = false + lockAtStart = false + } else if (cwDistanceFromStart > totalCircleDegrees) { // If the user is touching outside of the start AND end + userIsMovingPointer = false return false } else if (touchEventRadius >= innerRadius && touchEventRadius <= outerRadius) { // If the user is touching near the circle setProgressBasedOnAngle(touchAngle) - mPointerHaloPaint.alpha = mPointerAlphaOnTouch - mPointerHaloPaint.color = mPointerHaloColorOnTouch + pointerHaloPaint.alpha = pointerAlphaOnTouch + pointerHaloPaint.color = pointerHaloColorOnTouch recalculateAll() invalidate() - mOnCircularSeekBarChangeListener?.onStartTrackingTouch(this) - mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) - mUserIsMovingPointer = true - mLockAtEnd = false - mLockAtStart = false + onCircularSeekBarChangeListener?.onStartTrackingTouch(this) + onCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + userIsMovingPointer = true + lockAtEnd = false + lockAtStart = false } else { // If the user is not touching near the circle - mUserIsMovingPointer = false + userIsMovingPointer = false return false } } - MotionEvent.ACTION_MOVE -> if (mUserIsMovingPointer) { - val smallInCircle = mTotalCircleDegrees / 3f - var cwPointerFromStart = mPointerPosition - mStartAngle + MotionEvent.ACTION_MOVE -> if (userIsMovingPointer) { + val smallInCircle = totalCircleDegrees / 3f + var cwPointerFromStart = pointerPosition - startAngle cwPointerFromStart = if (cwPointerFromStart < 0) cwPointerFromStart + 360f else cwPointerFromStart val touchOverStart = ccwDistanceFromStart < smallInCircle val touchOverEnd = cwDistanceFromEnd < smallInCircle val pointerNearStart = cwPointerFromStart < smallInCircle - val pointerNearEnd = cwPointerFromStart > mTotalCircleDegrees - smallInCircle - val progressNearZero = mProgress < mMax / 3f - val progressNearMax = mProgress > mMax / 3f * 2f + val pointerNearEnd = cwPointerFromStart > totalCircleDegrees - smallInCircle + val progressNearZero = progressActual < max / 3f + val progressNearMax = progressActual > max / 3f * 2f if (progressNearMax) { // logic for end lock. if (pointerNearStart) { // negative end - mLockAtEnd = touchOverStart + lockAtEnd = touchOverStart } else if (pointerNearEnd) { // positive end - mLockAtEnd = touchOverEnd + lockAtEnd = touchOverEnd } } else if (progressNearZero && isNegativeEnabled) { // logic for negative flip - if (touchOverEnd) mIsInNegativeHalf = false else if (touchOverStart) { - mIsInNegativeHalf = true + if (touchOverEnd) isInNegativeHalf = false else if (touchOverStart) { + isInNegativeHalf = true } } else if (progressNearZero) { // logic for start lock if (pointerNearStart) { - mLockAtStart = touchOverStart + lockAtStart = touchOverStart } } - if (mLockAtStart && isLockEnabled) { + if (lockAtStart && isLockEnabled) { // TODO: Add a check if mProgress is already 0, in which case don't call the listener - mProgress = 0f + progressActual = 0f recalculateAll() invalidate() - mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) - } else if (mLockAtEnd && isLockEnabled) { - mProgress = mMax + onCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + } else if (lockAtEnd && isLockEnabled) { + progressActual = max recalculateAll() invalidate() - mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) - } else if (mMoveOutsideCircle || touchEventRadius <= outerRadius) { - if (cwDistanceFromStart <= mTotalCircleDegrees) { + onCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + } else if (moveOutsideCircle || touchEventRadius <= outerRadius) { + if (cwDistanceFromStart <= totalCircleDegrees) { setProgressBasedOnAngle(touchAngle) } recalculateAll() invalidate() - mOnCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) + onCircularSeekBarChangeListener?.onProgressChanged(this, progress, true) } } else { return false } MotionEvent.ACTION_UP -> { - mPointerHaloPaint.alpha = mPointerAlpha - mPointerHaloPaint.color = mPointerHaloColor - if (mUserIsMovingPointer) { - mUserIsMovingPointer = false + pointerHaloPaint.alpha = pointerAlpha + pointerHaloPaint.color = pointerHaloColor + if (userIsMovingPointer) { + userIsMovingPointer = false invalidate() - mOnCircularSeekBarChangeListener?.onStopTrackingTouch(this) + onCircularSeekBarChangeListener?.onStopTrackingTouch(this) } else { return false } } MotionEvent.ACTION_CANCEL -> { - mPointerHaloPaint.alpha = mPointerAlpha - mPointerHaloPaint.color = mPointerHaloColor - mUserIsMovingPointer = false + pointerHaloPaint.alpha = pointerAlpha + pointerHaloPaint.color = pointerHaloColor + userIsMovingPointer = false invalidate() } } @@ -1092,23 +1093,23 @@ class CircularSeekBar @JvmOverloads constructor( val superState = super.onSaveInstanceState() val state = Bundle() state.putParcelable("PARENT", superState) - state.putFloat("MAX", mMax) - state.putFloat("PROGRESS", mProgress) - state.putInt("mCircleColor", mCircleColor) - state.putInt("mCircleProgressColor", mCircleProgressColor) - state.putInt("mPointerColor", mPointerColor) - state.putInt("mPointerHaloColor", mPointerHaloColor) - state.putInt("mPointerHaloColorOnTouch", mPointerHaloColorOnTouch) - state.putInt("mPointerAlpha", mPointerAlpha) - state.putInt("mPointerAlphaOnTouch", mPointerAlphaOnTouch) - state.putFloat("mPointerAngle", mPointerAngle) - state.putBoolean("mDisablePointer", mDisablePointer) - state.putBoolean("mLockEnabled", isLockEnabled) - state.putBoolean("mNegativeEnabled", isNegativeEnabled) - state.putBoolean("mDisableProgressGlow", mDisableProgressGlow) - state.putBoolean("mIsInNegativeHalf", mIsInNegativeHalf) - state.putInt("mCircleStyle", mCircleStyle.ordinal) - state.putBoolean("mHideProgressWhenEmpty", mHideProgressWhenEmpty) + state.putFloat("MAX", max) + state.putFloat("PROGRESS", progressActual) + state.putInt("circleColor", circleColor) + state.putInt("circleProgressColor", circleProgressColor) + state.putInt("pointerColor", pointerColor) + state.putInt("pointerHaloColor", pointerHaloColor) + state.putInt("pointerHaloColorOnTouch", pointerHaloColorOnTouch) + state.putInt("pointerAlpha", pointerAlpha) + state.putInt("pointerAlphaOnTouch", pointerAlphaOnTouch) + state.putFloat("pointerAngle", pointerAngle) + state.putBoolean("disablePointer", disablePointer) + state.putBoolean("lockEnabled", isLockEnabled) + state.putBoolean("negativeEnabled", isNegativeEnabled) + state.putBoolean("disableProgressGlow", disableProgressGlow) + state.putBoolean("isInNegativeHalf", isInNegativeHalf) + state.putInt("circleStyle", circleStyle.ordinal) + state.putBoolean("hideProgressWhenEmpty", hideProgressWhenEmpty) return state } @@ -1116,29 +1117,29 @@ class CircularSeekBar @JvmOverloads constructor( val savedState = state as Bundle val superState = savedState.getParcelable("PARENT") super.onRestoreInstanceState(superState) - mMax = savedState.getFloat("MAX") - mProgress = savedState.getFloat("PROGRESS") - mCircleColor = savedState.getInt("mCircleColor") - mCircleProgressColor = savedState.getInt("mCircleProgressColor") - mPointerColor = savedState.getInt("mPointerColor") - mPointerHaloColor = savedState.getInt("mPointerHaloColor") - mPointerHaloColorOnTouch = savedState.getInt("mPointerHaloColorOnTouch") - mPointerAlpha = savedState.getInt("mPointerAlpha") - mPointerAlphaOnTouch = savedState.getInt("mPointerAlphaOnTouch") - mPointerAngle = savedState.getFloat("mPointerAngle") - mDisablePointer = savedState.getBoolean("mDisablePointer") - isLockEnabled = savedState.getBoolean("mLockEnabled") - isNegativeEnabled = savedState.getBoolean("mNegativeEnabled") - mDisableProgressGlow = savedState.getBoolean("mDisableProgressGlow") - mIsInNegativeHalf = savedState.getBoolean("mIsInNegativeHalf") - mCircleStyle = Cap.values()[savedState.getInt("mCircleStyle")] - mHideProgressWhenEmpty = savedState.getBoolean("mHideProgressWhenEmpty") + max = savedState.getFloat("MAX") + progressActual = savedState.getFloat("PROGRESS") + circleColor = savedState.getInt("circleColor") + circleProgressColor = savedState.getInt("circleProgressColor") + pointerColor = savedState.getInt("pointerColor") + pointerHaloColor = savedState.getInt("pointerHaloColor") + pointerHaloColorOnTouch = savedState.getInt("pointerHaloColorOnTouch") + pointerAlpha = savedState.getInt("pointerAlpha") + pointerAlphaOnTouch = savedState.getInt("pointerAlphaOnTouch") + pointerAngle = savedState.getFloat("pointerAngle") + disablePointer = savedState.getBoolean("disablePointer") + isLockEnabled = savedState.getBoolean("lockEnabled") + isNegativeEnabled = savedState.getBoolean("negativeEnabled") + disableProgressGlow = savedState.getBoolean("disableProgressGlow") + isInNegativeHalf = savedState.getBoolean("isInNegativeHalf") + circleStyle = Cap.values()[savedState.getInt("circleStyle")] + hideProgressWhenEmpty = savedState.getBoolean("hideProgressWhenEmpty") initPaints() recalculateAll() } fun setOnSeekBarChangeListener(l: OnCircularSeekBarChangeListener?) { - mOnCircularSeekBarChangeListener = l + onCircularSeekBarChangeListener = l } /** From 2ebc5041f76533408d0276cfa728fa15c0b3dd93 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 11 May 2022 16:47:36 -0400 Subject: [PATCH 07/17] Adds the ability to have an inner fill color on the pointer and specify the width. Deprecates less clear name `pointerStrokeWidth` with `pointerWidth` --- README.md | 2 +- app/src/main/res/layout/activity_main.xml | 11 ++- .../lib/circularseekbar/CircularSeekBar.kt | 98 ++++++++++++++++--- circularSeekBar/src/main/res/values/attrs.xml | 4 + 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4295c46b..89ddb6e8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ app:cs_circle_x_radius = "dimension" app:cs_circle_y_radius = "dimension" app:cs_circle_stroke_width = "dimension" app:cs_disable_pointer = "boolean" -app:cs_pointer_stroke_width = "dimension" +app:cs_pointer_width = "dimension" app:cs_pointer_halo_width = "dimension" app:cs_pointer_halo_border_width = "dimension" app:cs_circle_fill = "color" diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0ed82c8b..c726b1ab 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,7 +23,7 @@ app:cs_negative_enabled="true" app:cs_circle_style="butt" app:cs_circle_stroke_width="48dp" - app:cs_pointer_stroke_width="48dp" + app:cs_pointer_width="48dp" app:cs_pointer_angle="44"/> { // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched - val pointerRadiusDegrees = (pointerStrokeWidth * 180 / (Math.PI * Math.max( + val pointerRadiusDegrees = (pointerWidth * 180 / (Math.PI * Math.max( circleHeight, circleWidth ))).toFloat() @@ -1098,6 +1168,7 @@ class CircularSeekBar @JvmOverloads constructor( state.putInt("circleColor", circleColor) state.putInt("circleProgressColor", circleProgressColor) state.putInt("pointerColor", pointerColor) + state.putInt("pointerFillColor", pointerFillColor) state.putInt("pointerHaloColor", pointerHaloColor) state.putInt("pointerHaloColorOnTouch", pointerHaloColorOnTouch) state.putInt("pointerAlpha", pointerAlpha) @@ -1122,6 +1193,7 @@ class CircularSeekBar @JvmOverloads constructor( circleColor = savedState.getInt("circleColor") circleProgressColor = savedState.getInt("circleProgressColor") pointerColor = savedState.getInt("pointerColor") + pointerFillColor = savedState.getInt("pointerFillColor") pointerHaloColor = savedState.getInt("pointerHaloColor") pointerHaloColorOnTouch = savedState.getInt("pointerHaloColorOnTouch") pointerAlpha = savedState.getInt("pointerAlpha") @@ -1176,7 +1248,7 @@ class CircularSeekBar @JvmOverloads constructor( private val DEFAULT_CIRCLE_STYLE = Cap.ROUND.ordinal private const val DEFAULT_CIRCLE_X_RADIUS = 30f private const val DEFAULT_CIRCLE_Y_RADIUS = 30f - private const val DEFAULT_POINTER_STROKE_WIDTH = 14f + private const val DEFAULT_POINTER_WIDTH = 14f private const val DEFAULT_POINTER_HALO_WIDTH = 6f private const val DEFAULT_POINTER_HALO_BORDER_WIDTH = 0f private const val DEFAULT_CIRCLE_STROKE_WIDTH = 5f @@ -1188,6 +1260,8 @@ class CircularSeekBar @JvmOverloads constructor( private const val DEFAULT_CIRCLE_COLOR = Color.DKGRAY private val DEFAULT_CIRCLE_PROGRESS_COLOR = Color.argb(235, 74, 138, 255) private val DEFAULT_POINTER_COLOR = Color.argb(235, 74, 138, 255) + private const val DEFAULT_POINTER_FILL_COLOR = Color.TRANSPARENT + private val DEFAULT_POINTER_FILL_INNER_STROKE_WIDTH = 0f private val DEFAULT_POINTER_HALO_COLOR = Color.argb(135, 74, 138, 255) private val DEFAULT_POINTER_HALO_COLOR_ONTOUCH = Color.argb(135, 74, 138, 255) private const val DEFAULT_CIRCLE_FILL_COLOR = Color.TRANSPARENT diff --git a/circularSeekBar/src/main/res/values/attrs.xml b/circularSeekBar/src/main/res/values/attrs.xml index 108b67d5..52139341 100644 --- a/circularSeekBar/src/main/res/values/attrs.xml +++ b/circularSeekBar/src/main/res/values/attrs.xml @@ -18,6 +18,8 @@ + + @@ -25,6 +27,8 @@ + + From 1da0714e324d65cbc0cfd97443cbbe6975f21e7a Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 18 May 2022 12:49:08 -0400 Subject: [PATCH 08/17] Removes fill from square pointer example --- app/src/main/res/layout/activity_main.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c726b1ab..826ca484 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -86,7 +86,6 @@ android:layout_marginTop="@dimen/seekbar_margin" android:layout_marginLeft="@dimen/seekbar_margin" android:layout_marginRight="@dimen/seekbar_margin" - app:cs_pointer_fill_color="@android:color/white" app:cs_negative_enabled="false" app:cs_circle_style="square" app:cs_circle_stroke_width="8dp" From 097d674570fbca0b9dc61dec8c277fe6361811f6 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Mon, 23 May 2022 11:16:25 -0400 Subject: [PATCH 09/17] Reverts changes to to api names --- README.md | 4 +- app/src/main/res/layout/activity_main.xml | 8 ++-- .../lib/circularseekbar/CircularSeekBar.kt | 45 +++++-------------- circularSeekBar/src/main/res/values/attrs.xml | 2 - 4 files changed, 17 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index daf05a07..4f038360 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ app:cs_circle_x_radius = "dimension" app:cs_circle_y_radius = "dimension" app:cs_circle_stroke_width = "dimension" app:cs_disable_pointer = "boolean" -app:cs_pointer_width = "dimension" +app:cs_pointer_stroke_width = "dimension" app:cs_pointer_halo_width = "dimension" app:cs_pointer_halo_border_width = "dimension" app:cs_circle_fill = "color" @@ -71,6 +71,8 @@ app:cs_start_angle = "float" app:cs_end_angle = "float" app:cs_disable_progress_glow = "boolean" app:cs_hide_progress_when_empty = "boolean" +"cs_pointer_fill_color" = "color" +"cs_pointer_fill_inner_stroke_width" = "dimension" ``` ## Appreciation diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 826ca484..ba2e7f03 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,7 +23,7 @@ app:cs_negative_enabled="true" app:cs_circle_style="butt" app:cs_circle_stroke_width="48dp" - app:cs_pointer_width="48dp" + app:cs_pointer_stroke_width="48dp" app:cs_pointer_angle="44"/> { // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched - val pointerRadiusDegrees = (pointerWidth * 180 / (Math.PI * Math.max( + val pointerRadiusDegrees = (pointerStrokeWidth * 180 / (Math.PI * Math.max( circleHeight, circleWidth ))).toFloat() diff --git a/circularSeekBar/src/main/res/values/attrs.xml b/circularSeekBar/src/main/res/values/attrs.xml index 52139341..a10f55b8 100644 --- a/circularSeekBar/src/main/res/values/attrs.xml +++ b/circularSeekBar/src/main/res/values/attrs.xml @@ -18,8 +18,6 @@ - - From 07060b77e37fccfcfb2fc14d824e3d6df755ee6c Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Mon, 23 May 2022 11:51:33 -0400 Subject: [PATCH 10/17] Ensures `pointerFillInnerStrokeWidth` is never negative --- .../me/tankery/lib/circularseekbar/CircularSeekBar.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index 568cb582..f2003a79 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -168,11 +168,15 @@ class CircularSeekBar @JvmOverloads constructor( * The inner stroke width of the pointer (in pixels). * * Sets the inner pointer stroke width. - * @param width the width of the pointer stroke + * @param width the width of the pointer stroke. Must be a positive number */ var pointerFillInnerStrokeWidth = DEFAULT_POINTER_FILL_INNER_STROKE_WIDTH set(width) { - field = width + field = if (width < 0){ + 0f + } else { + width + } initPaints() recalculateAll() invalidate() From 0d7c056dbc7945a7d952439cbbc6ae80ad808473 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 1 Jun 2022 11:03:39 -0400 Subject: [PATCH 11/17] Adds name space to README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f038360..9d72f82b 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ app:cs_start_angle = "float" app:cs_end_angle = "float" app:cs_disable_progress_glow = "boolean" app:cs_hide_progress_when_empty = "boolean" -"cs_pointer_fill_color" = "color" -"cs_pointer_fill_inner_stroke_width" = "dimension" +app:cs_pointer_fill_color = "color" +app:cs_pointer_fill_inner_stroke_width = "dimension" ``` ## Appreciation From ff29588b8db15982a52c8b01fd883002ddc9a5d3 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 1 Jun 2022 11:06:25 -0400 Subject: [PATCH 12/17] Reverts name change for DEFAULT_POINTER_STROKE_WIDTH --- .../tankery/lib/circularseekbar/CircularSeekBar.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index f2003a79..6a7142c5 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -29,14 +29,8 @@ package me.tankery.lib.circularseekbar import android.content.Context import android.content.res.TypedArray -import android.graphics.Paint -import android.graphics.Path +import android.graphics.* import android.graphics.Paint.Cap -import android.graphics.RectF -import android.graphics.Color -import android.graphics.BlurMaskFilter -import android.graphics.PathMeasure -import android.graphics.Canvas import android.os.Build import android.os.Bundle import android.os.Parcelable @@ -578,7 +572,7 @@ class CircularSeekBar @JvmOverloads constructor( pointerStrokeWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_pointer_stroke_width, - DEFAULT_POINTER_WIDTH + DEFAULT_POINTER_STROKE_WIDTH ) pointerHaloWidth = attrArray.getDimension( R.styleable.cs_CircularSeekBar_cs_pointer_halo_width, @@ -1233,7 +1227,7 @@ class CircularSeekBar @JvmOverloads constructor( private val DEFAULT_CIRCLE_STYLE = Cap.ROUND.ordinal private const val DEFAULT_CIRCLE_X_RADIUS = 30f private const val DEFAULT_CIRCLE_Y_RADIUS = 30f - private const val DEFAULT_POINTER_WIDTH = 14f + private const val DEFAULT_POINTER_STROKE_WIDTH = 14f private const val DEFAULT_POINTER_HALO_WIDTH = 6f private const val DEFAULT_POINTER_HALO_BORDER_WIDTH = 0f private const val DEFAULT_CIRCLE_STROKE_WIDTH = 5f From 725f9518b2594f9e4e00975f46e4cae7ec995935 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 1 Jun 2022 12:37:45 -0400 Subject: [PATCH 13/17] Renames pointer fill properties to the much clearer name pointer overlay --- README.md | 4 +- app/src/main/res/layout/activity_main.xml | 6 +-- .../lib/circularseekbar/CircularSeekBar.kt | 51 ++++++++++--------- circularSeekBar/src/main/res/values/attrs.xml | 32 ++++++------ 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 9d72f82b..698249ab 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ app:cs_start_angle = "float" app:cs_end_angle = "float" app:cs_disable_progress_glow = "boolean" app:cs_hide_progress_when_empty = "boolean" -app:cs_pointer_fill_color = "color" -app:cs_pointer_fill_inner_stroke_width = "dimension" +app:cs_pointer_overlay_color = "color" +app:cs_pointer_overlay_border_width = "dimension" ``` ## Appreciation diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ba2e7f03..6db6a974 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -61,13 +61,13 @@ android:layout_marginTop="@dimen/seekbar_margin" android:layout_marginLeft="@dimen/seekbar_margin" android:layout_marginRight="@dimen/seekbar_margin" - app:cs_pointer_fill_color="@android:color/white" - app:cs_pointer_fill_inner_stroke_width="10dp" + app:cs_pointer_overlay_color="@android:color/white" + app:cs_pointer_overlay_border_width="10dp" app:cs_negative_enabled="true" app:cs_circle_style="round" app:cs_circle_stroke_width="8dp" app:cs_pointer_stroke_width="24dp" - app:cs_pointer_halo_width="@dimen/seekbar_holo_width"/> + app:cs_pointer_halo_width="@dimen/seekbar_holo_width" /> - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + From c0634f7410db9447cf609474f8ec5f0ff9084c98 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 1 Jun 2022 12:46:33 -0400 Subject: [PATCH 14/17] Fixes bug related to using `Paint.Style.FILL_AND_STROKE` instead of `Paint.Style.STROKE` that caused a visual bug when used in conjuction with `pointerAngle` --- .../main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index 3969abde..c7a4221c 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -744,7 +744,7 @@ class CircularSeekBar @JvmOverloads constructor( pointerOverlayPaint.set(pointerPaint) pointerOverlayPaint.color = pointerOverlayColor - pointerOverlayPaint.style = Paint.Style.FILL_AND_STROKE + pointerOverlayPaint.style = Paint.Style.STROKE pointerOverlayPaint.strokeWidth = (pointerStrokeWidth - pointerOverlayBorderWidth).coerceAtLeast(0f) From 402a987736f01e99fcbb11b094d14c7b28be365a Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Wed, 1 Jun 2022 12:55:23 -0400 Subject: [PATCH 15/17] Removes * from imports --- .../me/tankery/lib/circularseekbar/CircularSeekBar.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index c7a4221c..39da78a5 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -29,7 +29,13 @@ package me.tankery.lib.circularseekbar import android.content.Context import android.content.res.TypedArray -import android.graphics.* +import android.graphics.Paint +import android.graphics.Path +import android.graphics.RectF +import android.graphics.Color +import android.graphics.BlurMaskFilter +import android.graphics.PathMeasure +import android.graphics.Canvas import android.graphics.Paint.Cap import android.os.Build import android.os.Bundle From ef92ce378822b59c9f0161d9a28fc4f26eb6e980 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Mon, 20 Jun 2022 13:30:03 -0400 Subject: [PATCH 16/17] Adjust imports order --- .../me/tankery/lib/circularseekbar/CircularSeekBar.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index 39da78a5..b754d415 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -29,14 +29,14 @@ package me.tankery.lib.circularseekbar import android.content.Context import android.content.res.TypedArray -import android.graphics.Paint -import android.graphics.Path -import android.graphics.RectF -import android.graphics.Color import android.graphics.BlurMaskFilter -import android.graphics.PathMeasure import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint import android.graphics.Paint.Cap +import android.graphics.Path +import android.graphics.PathMeasure +import android.graphics.RectF import android.os.Build import android.os.Bundle import android.os.Parcelable From 7c3e0cc4f068d35d9136a2e0a0a406a5cc6766e4 Mon Sep 17 00:00:00 2001 From: Jonathan Morton Date: Mon, 20 Jun 2022 13:37:48 -0400 Subject: [PATCH 17/17] Saves instance state of pointerOverlayBorderWidth --- .../main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt index b754d415..1897bcf2 100644 --- a/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt +++ b/circularSeekBar/src/main/java/me/tankery/lib/circularseekbar/CircularSeekBar.kt @@ -1155,6 +1155,7 @@ class CircularSeekBar @JvmOverloads constructor( state.putInt("circleProgressColor", circleProgressColor) state.putInt("pointerColor", pointerColor) state.putInt("pointerOverlayColor", pointerOverlayColor) + state.putFloat("pointerOverlayBorderWidth", pointerOverlayBorderWidth) state.putInt("pointerHaloColor", pointerHaloColor) state.putInt("pointerHaloColorOnTouch", pointerHaloColorOnTouch) state.putInt("pointerAlpha", pointerAlpha) @@ -1180,6 +1181,7 @@ class CircularSeekBar @JvmOverloads constructor( circleProgressColor = savedState.getInt("circleProgressColor") pointerColor = savedState.getInt("pointerColor") pointerOverlayColor = savedState.getInt("pointerOverlayColor") + pointerOverlayBorderWidth = savedState.getFloat("pointerOverlayBorderWidth") pointerHaloColor = savedState.getInt("pointerHaloColor") pointerHaloColorOnTouch = savedState.getInt("pointerHaloColorOnTouch") pointerAlpha = savedState.getInt("pointerAlpha")