diff --git a/README.md b/README.md index 5557888..3ce756b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -### Jetpack Compose Colorful Customizable Sliders +# Jetpack Compose Colorful Customizable Sliders Colorful sliders that can have Solid or Gradient colors for thumb or track which can have thumb and track with varying sizes, borders with solid or gradient colors. And Sliders with emojis, or custom Composables like **Icon**. -|Properties | Dimensions | Slider with Icon | Slider with Label | Gradient | +| Slider with Icon | Gradient | Properties | Slider with Label | Dimensions | | ----------|-----------| -----------| -----------| -----------| -| | | | | | +| | | | | | -### ColorfulSlider +## ColorfulSlider Sliders that can use Color or gradient for track, thumb, or tick colors with custom thumb and track heights. @@ -122,7 +122,7 @@ data class SliderBrushColor( } ``` -### SliderWithLabel +## SliderWithLabel Sliders that can move a label above the Slider and display progress @@ -173,7 +173,7 @@ SliderWithLabel( ) ``` -### ColorfulIconSlider +## ColorfulIconSlider Sliders that can use any Composable for thumb and use Color or gradient for track, thumb, or tick colors with custom thumb and track heights. diff --git a/app/build.gradle b/app/build.gradle index 85e4dd8..2745ea9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,15 +50,19 @@ dependencies { implementation project(':slider') implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' + implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' implementation 'androidx.activity:activity-compose:1.4.0' - implementation 'com.google.accompanist:accompanist-systemuicontroller:0.24.3-alpha' - implementation "com.google.accompanist:accompanist-pager:0.24.3-alpha" - implementation "com.google.accompanist:accompanist-pager-indicators:0.24.3-alpha" + def accompanist_version = "0.24.6-alpha" + // Accompanist + implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version" + implementation "com.google.accompanist:accompanist-pager:$accompanist_version" + implementation "com.google.accompanist:accompanist-drawablepainter:$accompanist_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/app/src/main/java/com/smarttoolfactory/composematerialslider/MainActivity.kt b/app/src/main/java/com/smarttoolfactory/composematerialslider/MainActivity.kt index 4da131e..8b15302 100644 --- a/app/src/main/java/com/smarttoolfactory/composematerialslider/MainActivity.kt +++ b/app/src/main/java/com/smarttoolfactory/composematerialslider/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable @@ -12,10 +11,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.google.accompanist.pager.* import com.smarttoolfactory.composematerialslider.demo.* -import com.smarttoolfactory.composematerialslider.ui.theme.BlueSmart import com.smarttoolfactory.composematerialslider.ui.theme.BlueSmartDark import com.smarttoolfactory.composematerialslider.ui.theme.ComposeMaterialSliderTheme import kotlinx.coroutines.launch @@ -54,11 +51,7 @@ private fun HomeContent() { // Our selected tab is our current page selectedTabIndex = pagerState.currentPage, // Override the indicator, using the provided pagerTabIndicatorOffset modifier - indicator = { tabPositions -> - TabRowDefaults.Indicator( - Modifier.pagerTabIndicatorOffset(pagerState, tabPositions) - ) - } + indicator = {} ) { // Add tabs for all of our pages tabList.forEachIndexed { index, title -> diff --git a/build.gradle b/build.gradle index bfbdce8..8d3938d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { ext { - compose_version = '1.2.0-alpha06' + compose_version = '1.2.0-alpha07' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.2' apply false - id 'com.android.library' version '7.1.2' apply false + id 'com.android.application' version '7.1.3' apply false + id 'com.android.library' version '7.1.3' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } diff --git a/settings.gradle b/settings.gradle index 8705b9c..20059d5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url 'https://jitpack.io'} } } rootProject.name = "Compose Material Slider" diff --git a/slider/build.gradle b/slider/build.gradle index 877bb7b..8c47357 100644 --- a/slider/build.gradle +++ b/slider/build.gradle @@ -44,19 +44,15 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' + + implementation 'com.github.SmartToolFactory:Compose-Extended-Gestures:1.0.0' // Jetpack Compose implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.ui:ui-tooling:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.material:material-icons-extended:$compose_version" implementation "androidx.compose.runtime:runtime:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' - implementation 'androidx.activity:activity-compose:1.4.0' - testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/slider/src/main/java/com/smarttoolfactory/slider/ColorfulIconSlider.kt b/slider/src/main/java/com/smarttoolfactory/slider/ColorfulIconSlider.kt index 8e5566c..250d279 100644 --- a/slider/src/main/java/com/smarttoolfactory/slider/ColorfulIconSlider.kt +++ b/slider/src/main/java/com/smarttoolfactory/slider/ColorfulIconSlider.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.* -import com.smarttoolfactory.slider.gesture.pointerMotionEvents +import com.smarttoolfactory.gesture.pointerMotionEvents /** * Material Slider allows to choose height for track and thumb radius and selection between diff --git a/slider/src/main/java/com/smarttoolfactory/slider/ColorfulSlider.kt b/slider/src/main/java/com/smarttoolfactory/slider/ColorfulSlider.kt index cc49dc1..f8f2edf 100644 --- a/slider/src/main/java/com/smarttoolfactory/slider/ColorfulSlider.kt +++ b/slider/src/main/java/com/smarttoolfactory/slider/ColorfulSlider.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import com.smarttoolfactory.slider.gesture.pointerMotionEvents +import com.smarttoolfactory.gesture.pointerMotionEvents import kotlin.math.abs /** diff --git a/slider/src/main/java/com/smarttoolfactory/slider/gesture/AwaitPointerMotionEvent.kt b/slider/src/main/java/com/smarttoolfactory/slider/gesture/AwaitPointerMotionEvent.kt deleted file mode 100644 index 63059f7..0000000 --- a/slider/src/main/java/com/smarttoolfactory/slider/gesture/AwaitPointerMotionEvent.kt +++ /dev/null @@ -1,165 +0,0 @@ -package com.smarttoolfactory.slider.gesture - -import androidx.compose.foundation.gestures.* -import androidx.compose.ui.input.pointer.* -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -/** - * Reads [awaitFirstDown], and [awaitPointerEvent] to - * get [PointerInputChange] and motion event states - * [onDown], [onMove], and [onUp]. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove one or multiple pointers are being moved on screen. - * @param onUp last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - - */ -suspend fun PointerInputScope.detectMotionEvents( - onDown: (PointerInputChange) -> Unit = {}, - onMove: (PointerInputChange) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) { - coroutineScope { - forEachGesture { - awaitPointerEventScope { - // Wait for at least one pointer to press down, and set first contact position - val down: PointerInputChange = awaitFirstDown() - onDown(down) - - var pointer = down - // Main pointer is the one that is down initially - var pointerId = down.id - - // If a move event is followed fast enough down is skipped, especially by Canvas - // to prevent it we add delay after first touch - var waitedAfterDown = false - - launch { - delay(delayAfterDownInMillis) - waitedAfterDown = true - } - - while (true) { - - val event: PointerEvent = awaitPointerEvent() - - val anyPressed = event.changes.any { it.pressed } - - // There are at least one pointer pressed - if (anyPressed) { - // Get pointer that is down, if first pointer is up - // get another and use it if other pointers are also down - // event.changes.first() doesn't return same order - val pointerInputChange = - event.changes.firstOrNull { it.id == pointerId } - ?: event.changes.first() - - // Next time will check same pointer with this id - pointerId = pointerInputChange.id - pointer = pointerInputChange - - if (waitedAfterDown) { - onMove(pointer) - } - } else { - // All of the pointers are up - onUp(pointer) - break - } - } - } - } - } -} - -/** - * Reads [awaitFirstDown], and [awaitPointerEvent] to - * get [PointerInputChange] and motion event states - * [onDown], [onMove], and [onUp]. Unlike overload of this function [onMove] returns - * list of [PointerInputChange] to get data about all pointers that are on the screen. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove one or multiple pointers are being moved on screen. - * @param onUp last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - */ -suspend fun PointerInputScope.detectMotionEventsAsList( - onDown: (PointerInputChange) -> Unit = {}, - onMove: (List) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) { - - coroutineScope { - forEachGesture { - awaitPointerEventScope { - // Wait for at least one pointer to press down, and set first contact position - val down: PointerInputChange = awaitFirstDown() - onDown(down) - - var pointer = down - // Main pointer is the one that is down initially - var pointerId = down.id - - // If a move event is followed fast enough down is skipped, especially by Canvas - // to prevent it we add delay after first touch - var waitedAfterDown = false - - launch { - delay(delayAfterDownInMillis) - waitedAfterDown = true - } - - while (true) { - - val event: PointerEvent = awaitPointerEvent() - - val anyPressed = event.changes.any { it.pressed } - - // There are at least one pointer pressed - if (anyPressed) { - // Get pointer that is down, if first pointer is up - // get another and use it if other pointers are also down - // event.changes.first() doesn't return same order - val pointerInputChange = - event.changes.firstOrNull { it.id == pointerId } - ?: event.changes.first() - - // Next time will check same pointer with this id - pointerId = pointerInputChange.id - pointer = pointerInputChange - - if (waitedAfterDown) { - onMove(event.changes) - } - - } else { - // All of the pointers are up - onUp(pointer) - break - } - } - } - } - } -} diff --git a/slider/src/main/java/com/smarttoolfactory/slider/gesture/MotionEvent.kt b/slider/src/main/java/com/smarttoolfactory/slider/gesture/MotionEvent.kt deleted file mode 100644 index 31902bd..0000000 --- a/slider/src/main/java/com/smarttoolfactory/slider/gesture/MotionEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.smarttoolfactory.slider.gesture - -enum class MotionEvent { - Idle, Down, Move, Up -} diff --git a/slider/src/main/java/com/smarttoolfactory/slider/gesture/PointerMotionModifier.kt b/slider/src/main/java/com/smarttoolfactory/slider/gesture/PointerMotionModifier.kt deleted file mode 100644 index 721922d..0000000 --- a/slider/src/main/java/com/smarttoolfactory/slider/gesture/PointerMotionModifier.kt +++ /dev/null @@ -1,216 +0,0 @@ -package com.smarttoolfactory.slider.gesture - -import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.pointer.* - -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with a different [key1]. - */ -fun Modifier.pointerMotionEvents( - key1: Any? = Unit, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (PointerInputChange) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(key1) { - detectMotionEvents(onDown, onMove, onUp, delayAfterDownInMillis) - } -) -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with a different [key1] or [key2]. - */ -fun Modifier.pointerMotionEvents( - key1: Any?, - key2: Any?, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (PointerInputChange) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(key1, key2) { - pointerMotionEvents(onDown, onMove, onUp, delayAfterDownInMillis) - } -) - -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with any different [keys]. - */ -fun Modifier.pointerMotionEvents( - vararg keys: Any?, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (PointerInputChange) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(keys) { - detectMotionEvents(onDown, onMove, onUp, delayAfterDownInMillis) - } -) - -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with a different [key1]. - */ -fun Modifier.pointerMotionEventList( - key1: Any? = Unit, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (List) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(key1) { - detectMotionEventsAsList(onDown, onMove, onUp, delayAfterDownInMillis) - } -) - -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with a different [key1] or [key2]. - */ -fun Modifier.pointerMotionEventList( - key1: Any? = Unit, - key2: Any? = Unit, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (List) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(key1, key2) { - detectMotionEventsAsList(onDown, onMove, onUp, delayAfterDownInMillis) - } -) - -/** - * Create a modifier for processing pointer motion input within the region of the modified element. - * - * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then - * [onDown] is called at first pointer contact. - * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. - * When last pointer is up [onUp] is called. - * - * To prevent other pointer functions that call [awaitFirstDown] or [awaitPointerEvent] - * (scroll, swipe, detect functions) - * receiving changes call [PointerInputChange.consumeDownChange] in [onDown], - * and call [PointerInputChange.consumePositionChange] - * in [onMove] block. - * - * @param onDown is invoked when first pointer is down initially. - * @param onMove is invoked when one or multiple pointers are being moved on screen. - * @param onUp is invoked when last pointer is up - * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be - * required Composables like **Canvas** to process [onDown] before [onMove] - * - * The pointer input handling block will be cancelled and re-started when pointerInput - * is recomposed with any different [keys]. - */ -fun Modifier.pointerMotionEventList( - vararg keys: Any?, - onDown: (PointerInputChange) -> Unit = {}, - onMove: (List) -> Unit = {}, - onUp: (PointerInputChange) -> Unit = {}, - delayAfterDownInMillis: Long = 0L -) = this.then( - Modifier.pointerInput(keys) { - detectMotionEventsAsList(onDown, onMove, onUp, delayAfterDownInMillis) - } -)