Document TextPicker Composable and it's util classes, functions
This commit is contained in:
parent
be18d9859d
commit
ef15aca31f
15 changed files with 311 additions and 45 deletions
|
|
@ -59,9 +59,9 @@ fun TextPickerScreen() {
|
||||||
var selected by remember { mutableStateOf(0) }
|
var selected by remember { mutableStateOf(0) }
|
||||||
LinedTextPicker(
|
LinedTextPicker(
|
||||||
modifier = Modifier.defaultMinSize(minWidth = 200.dp),
|
modifier = Modifier.defaultMinSize(minWidth = 200.dp),
|
||||||
selected = selected,
|
selectedIndex = selected,
|
||||||
textForIndex = text::get,
|
textForIndex = text::get,
|
||||||
onSelectedChange = {
|
onSelectedIndexChange = {
|
||||||
selected = it
|
selected = it
|
||||||
},
|
},
|
||||||
textStyle = MaterialTheme.typography.h5,
|
textStyle = MaterialTheme.typography.h5,
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ fun NumberPickerScope.LinedInnerTextPicker(modifier: Modifier = Modifier) {
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
textForIndex = textForIndex,
|
textForIndex = textForIndex,
|
||||||
itemCount = itemCount,
|
itemCount = itemCount,
|
||||||
selected = selectedIndex,
|
selectedIndex = selectedIndex,
|
||||||
onSelectedChange = onSelectedIndexChange,
|
onSelectedIndexChange = onSelectedIndexChange,
|
||||||
onIndexDifferenceChanging = onIndexDifferenceChanging,
|
onIndexDifferenceChanging = onIndexDifferenceChanging,
|
||||||
state = state
|
state = state
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,45 @@ import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
||||||
import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator
|
import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator
|
||||||
import org.fknives.android.compose.picker.text.util.rememberTextPickerState
|
import org.fknives.android.compose.picker.text.util.rememberTextPickerState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customised version of TextPicker.
|
||||||
|
* Adds additional Dividers by [textPickerContent] and places them by customized [textPickerMeasurePolicy]
|
||||||
|
* The Default Dividers can be customised by giving parameters to [LinedTextPickerContent] as [textPickerContent].
|
||||||
|
*
|
||||||
|
* If no selection changes, snaps back to the selected element.
|
||||||
|
* Longer drags and letting go, animates scrolling over elements.
|
||||||
|
* Remembers the previously selected element and when selecting programmatically, scrolls to it..
|
||||||
|
* (Selected by UI action makes it previously selected before sending back the event, aka no double animation)
|
||||||
|
*
|
||||||
|
* @param modifier Standard Modifier for the Layout
|
||||||
|
* @param textForIndex Used to display Text for the given index. Indexes are between `0 until [itemCount]`.
|
||||||
|
* @param itemCount Number of Elements that can be selected
|
||||||
|
* @param selectedIndex Currently selected index. Must be between `0 until [itemCount]`
|
||||||
|
* @param onSelectedIndexChange Notified after animation, what the User selected through dragging of the UI.
|
||||||
|
* @param textStyle Default textStyle provided for the Text Composables inside the Layout.
|
||||||
|
* @param roundAround Behavioural change:
|
||||||
|
* Setting this to True, means the elements behave like a wheel, and the last element is above the first.
|
||||||
|
* Setting this to False, means the first element has no elements above it, and the last element has no element below it.
|
||||||
|
* @param onIndexDifferenceChanging Signals the animation changes, how much the current dragging is away from [selectedIndex].
|
||||||
|
* Negative values mean the index were decreased, Positive means it was increased.
|
||||||
|
* @param state Animation State for the TextPicker
|
||||||
|
* @param animator Uses state to Animate the Composable elements. Handles continous drag, calculating fling and snapping to an index.
|
||||||
|
* @param textPickerContent The actual Composables inside the TextPicker, by default it is the 4 Texts and the 2 Dividers
|
||||||
|
* @param textPickerMeasurePolicy The MeasurePolicy of the Layout, by default Translates the 4 Texts in [textPickerContent]
|
||||||
|
* according to [state] changed by [animator]
|
||||||
|
* @param pickerItem The Expected Text Composables in [textPickerContent].
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LinedTextPicker(
|
fun LinedTextPicker(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
textForIndex: (Int) -> String,
|
textForIndex: (Int) -> String,
|
||||||
itemCount: Int,
|
itemCount: Int,
|
||||||
selected: Int,
|
selectedIndex: Int,
|
||||||
onSelectedChange: (Int) -> Unit,
|
onSelectedIndexChange: (Int) -> Unit,
|
||||||
textStyle: TextStyle = LocalTextStyle.current,
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
roundAround: Boolean = TextPickerDefaults.roundAround,
|
roundAround: Boolean = TextPickerDefaults.roundAround,
|
||||||
onIndexDifferenceChanging: (Int) -> Unit = TextPickerDefaults.onIndexDifferenceChanging,
|
onIndexDifferenceChanging: (Int) -> Unit = TextPickerDefaults.onIndexDifferenceChanging,
|
||||||
state: TextPickerState = rememberTextPickerState(selected = selected, itemCount = itemCount),
|
state: TextPickerState = rememberTextPickerState(selected = selectedIndex, itemCount = itemCount),
|
||||||
animator: TextPickerAnimator = rememberTextPickerAnimator(roundAround = roundAround, onIndexDifferenceChanging = onIndexDifferenceChanging),
|
animator: TextPickerAnimator = rememberTextPickerAnimator(roundAround = roundAround, onIndexDifferenceChanging = onIndexDifferenceChanging),
|
||||||
textPickerContent: TextPickerContent = LinedTextPickerContent(),
|
textPickerContent: TextPickerContent = LinedTextPickerContent(),
|
||||||
textPickerMeasurePolicy: MeasurePolicy = remember(state) { LinedTextPickerMeasurePolicy(state) },
|
textPickerMeasurePolicy: MeasurePolicy = remember(state) { LinedTextPickerMeasurePolicy(state) },
|
||||||
|
|
@ -31,8 +59,8 @@ fun LinedTextPicker(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
textForIndex = textForIndex,
|
textForIndex = textForIndex,
|
||||||
itemCount = itemCount,
|
itemCount = itemCount,
|
||||||
selectedIndex = selected,
|
selectedIndex = selectedIndex,
|
||||||
onSelectedIndexChange = onSelectedChange,
|
onSelectedIndexChange = onSelectedIndexChange,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
roundAround = roundAround,
|
roundAround = roundAround,
|
||||||
onIndexDifferenceChanging = onIndexDifferenceChanging,
|
onIndexDifferenceChanging = onIndexDifferenceChanging,
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,48 @@ import androidx.compose.ui.layout.MeasurePolicy
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import org.fknives.android.compose.picker.text.content.DefaultTextPickerContent
|
import org.fknives.android.compose.picker.text.content.DefaultTextPickerContent
|
||||||
import org.fknives.android.compose.picker.text.content.DefaultTextPickerMeasurePolicy
|
import org.fknives.android.compose.picker.text.content.DefaultTextPickerMeasurePolicy
|
||||||
|
import org.fknives.android.compose.picker.text.content.TextPickerContent
|
||||||
import org.fknives.android.compose.picker.text.content.defaultNumberPickerTextAlphaModifier
|
import org.fknives.android.compose.picker.text.content.defaultNumberPickerTextAlphaModifier
|
||||||
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
||||||
import org.fknives.android.compose.picker.text.util.rememberDefaultMoveUnsafeToProperIndex
|
import org.fknives.android.compose.picker.text.util.rememberDefaultMoveUnsafeToProperIndex
|
||||||
import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator
|
import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator
|
||||||
import org.fknives.android.compose.picker.text.util.rememberTextPickerState
|
import org.fknives.android.compose.picker.text.util.rememberTextPickerState
|
||||||
import org.fknives.android.compose.picker.text.util.rememberWrappedTextForIndex
|
import org.fknives.android.compose.picker.text.util.rememberSafeTextForIndex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NumberPicker Custom Layout Composable which shows given text on each index.
|
||||||
|
* Shows 3 selections options (while animating may show 4).
|
||||||
|
* Scrolling the Composable moves between elements and at the end of the movement is selects the most middle element.
|
||||||
|
*
|
||||||
|
* If no selection changes, snaps back to the selected element.
|
||||||
|
* Longer drags and letting go, animates scrolling over elements.
|
||||||
|
* Remembers the previously selected element and when selecting programmatically, scrolls to it..
|
||||||
|
* (Selected by UI action makes it previously selected before sending back the event, aka no double animation)
|
||||||
|
*
|
||||||
|
* @param modifier Standard Modifier for the Layout
|
||||||
|
* @param textForIndex Used to display Text for the given index. Indexes are between `0 until [itemCount]`.
|
||||||
|
* @param itemCount Number of Elements that can be selected
|
||||||
|
* @param selectedIndex Currently selected index. Must be between `0 until [itemCount]`
|
||||||
|
* @param onSelectedIndexChange Notified after animation, what the User selected through dragging of the UI.
|
||||||
|
* @param textStyle Default textStyle provided for the Text Composables inside the Layout.
|
||||||
|
* @param roundAround Behavioural change:
|
||||||
|
* Setting this to True, means the elements behave like a wheel, and the last element is above the first.
|
||||||
|
* Setting this to False, means the first element has no elements above it, and the last element has no element below it.
|
||||||
|
* @param onIndexDifferenceChanging Signals the animation changes, how much the current dragging is away from [selectedIndex].
|
||||||
|
* Negative values mean the index were decreased, Positive means it was increased.
|
||||||
|
* @param state Animation State for the TextPicker
|
||||||
|
* @param animator Uses state to Animate the Composable elements. Handles continous drag, calculating fling and snapping to an index.
|
||||||
|
* @param textPickerContent The actual Composables inside the TextPicker, by default it is the 4 Texts
|
||||||
|
* @param textPickerMeasurePolicy The MeasurePolicy of the Layout, by default Translates the 4 Texts in [textPickerContent]
|
||||||
|
* according to [state] changed by [animator]
|
||||||
|
* @param pickerItem The Expected Text Composables in [textPickerContent].
|
||||||
|
* text is the given Text for the Item.
|
||||||
|
* translation is the movement of the element:
|
||||||
|
* - 1.0 -> selected
|
||||||
|
* - 0.5 -> unselected
|
||||||
|
* - 0.0 -> completely outside of boundaries.
|
||||||
|
* - movement between translation are expected to be proper (such as setting alpha based on the translation linearly).
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextPicker(
|
fun TextPicker(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -40,7 +75,12 @@ fun TextPicker(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val moveUnsafeToProperIndex: (Int) -> Int = rememberDefaultMoveUnsafeToProperIndex(itemCount = itemCount, roundAround = roundAround)
|
val moveUnsafeToProperIndex: (Int) -> Int = rememberDefaultMoveUnsafeToProperIndex(itemCount = itemCount, roundAround = roundAround)
|
||||||
val rememberTextForIndex = rememberWrappedTextForIndex(itemCount = itemCount, roundAround = roundAround, textForIndex = textForIndex)
|
val rememberTextForIndex = rememberSafeTextForIndex(
|
||||||
|
itemCount = itemCount,
|
||||||
|
roundAround = roundAround,
|
||||||
|
textForIndex = textForIndex,
|
||||||
|
moveUnsafeToProperIndex = moveUnsafeToProperIndex
|
||||||
|
)
|
||||||
|
|
||||||
Layout(
|
Layout(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -70,7 +110,6 @@ fun TextPicker(
|
||||||
textPickerContent.Content(
|
textPickerContent.Content(
|
||||||
textForIndex = rememberTextForIndex,
|
textForIndex = rememberTextForIndex,
|
||||||
item = pickerItem,
|
item = pickerItem,
|
||||||
moveUnsafeToProperIndex = moveUnsafeToProperIndex,
|
|
||||||
selected = moveUnsafeToProperIndex(selectedIndex + state.indexOffset),
|
selected = moveUnsafeToProperIndex(selectedIndex + state.indexOffset),
|
||||||
before1TranslatePercent = state.before1TranslatePercent,
|
before1TranslatePercent = state.before1TranslatePercent,
|
||||||
itemTranslatePercent = state.itemTranslatePercent,
|
itemTranslatePercent = state.itemTranslatePercent,
|
||||||
|
|
@ -83,16 +122,3 @@ fun TextPicker(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun interface TextPickerContent {
|
|
||||||
@Composable
|
|
||||||
fun Content(
|
|
||||||
textForIndex: (Int) -> String,
|
|
||||||
selected: Int,
|
|
||||||
moveUnsafeToProperIndex: (Int) -> Int,
|
|
||||||
before1TranslatePercent: Float,
|
|
||||||
itemTranslatePercent: Float,
|
|
||||||
after1TranslatePercent: Float,
|
|
||||||
after2TranslatePercent: Float,
|
|
||||||
item: @Composable (text: String, translation: Float) -> Unit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,21 @@ import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animator for [TextPicker]
|
||||||
|
*
|
||||||
|
* Handles 3 actions:
|
||||||
|
* - Continuous Dragging via [onDeltaY]
|
||||||
|
* - Scrolling Fling Animation via [flingAndSnap]
|
||||||
|
* - Scrolling newly selected item via [snapToIndex]
|
||||||
|
*
|
||||||
|
* @param flingDecaySpec AnimationSpec calculating expected scrolling by the given velocity. Used for the Fling Animation.
|
||||||
|
* @param flingAnimationSpec AnimationSpec used to do the actual scrolling animation, used in both animations.
|
||||||
|
* @param velocityMultiplier Controls Velocity to Slow or Fasten fling calculation, Raw Velocity is multiplied with this parameter.
|
||||||
|
* @param offsetLimiter Limits the offset animation. This can be used, to not let over scrolling happen after a given index or similar.
|
||||||
|
* @param onIndexDifferenceChanging Index Difference updates as animations and dragging happens.
|
||||||
|
* Negative values mean the index were decreased, Positive means it was increased.
|
||||||
|
*/
|
||||||
class TextPickerAnimator(
|
class TextPickerAnimator(
|
||||||
private val flingDecaySpec: DecayAnimationSpec<Float>,
|
private val flingDecaySpec: DecayAnimationSpec<Float>,
|
||||||
private val flingAnimationSpec: AnimationSpec<Float> = spring(0.75f),
|
private val flingAnimationSpec: AnimationSpec<Float> = spring(0.75f),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal State for [TextPicker]
|
||||||
|
*
|
||||||
|
* Holds the Scrolling [offset] changes and Text [itemSize]-s.
|
||||||
|
* From this the Translations and index changes are Derived.
|
||||||
|
*/
|
||||||
class TextPickerState(
|
class TextPickerState(
|
||||||
itemSizeState: MutableState<Float>,
|
itemSizeState: MutableState<Float>,
|
||||||
previousSelectedState: MutableState<Int>,
|
previousSelectedState: MutableState<Int>,
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,33 @@
|
||||||
package org.fknives.android.compose.picker.text.content
|
package org.fknives.android.compose.picker.text.content
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.fknives.android.compose.picker.text.TextPickerContent
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default [TextPickerContent] for [TextPicker][org.fknives.android.compose.picker.text.TextPicker].
|
||||||
|
*/
|
||||||
class DefaultTextPickerContent : TextPickerContent {
|
class DefaultTextPickerContent : TextPickerContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the actual content.
|
||||||
|
*
|
||||||
|
* Calculates the indexes around [selected], gets each item's text via [textForIndex].
|
||||||
|
* From that the [item]s are placed with their respective text and given translation.
|
||||||
|
*
|
||||||
|
* The items are expected to be places by [DefaultTextPickerContent]
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(
|
override fun Content(
|
||||||
textForIndex: (Int) -> String,
|
textForIndex: (Int) -> String,
|
||||||
selected: Int,
|
selected: Int,
|
||||||
moveUnsafeToProperIndex: (Int) -> Int,
|
|
||||||
before1TranslatePercent: Float,
|
before1TranslatePercent: Float,
|
||||||
itemTranslatePercent: Float,
|
itemTranslatePercent: Float,
|
||||||
after1TranslatePercent: Float,
|
after1TranslatePercent: Float,
|
||||||
after2TranslatePercent: Float,
|
after2TranslatePercent: Float,
|
||||||
item: @Composable (text: String, translation: Float) -> Unit
|
item: @Composable (text: String, translation: Float) -> Unit
|
||||||
) {
|
) {
|
||||||
val before1 = moveUnsafeToProperIndex(selected - 1)
|
val before1 = selected - 1
|
||||||
val after1 = moveUnsafeToProperIndex(selected + 1)
|
val after1 = selected + 1
|
||||||
val after2 = moveUnsafeToProperIndex(selected + 2)
|
val after2 = selected + 2
|
||||||
item(
|
item(
|
||||||
text = textForIndex(before1),
|
text = textForIndex(before1),
|
||||||
translation = before1TranslatePercent
|
translation = before1TranslatePercent
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Text Label inside a [TextPicker][org.fknives.android.compose.picker.text.TextPicker]
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultNumberPickerText(
|
fun DefaultNumberPickerText(
|
||||||
text: String,
|
text: String,
|
||||||
|
|
@ -19,6 +22,28 @@ fun DefaultNumberPickerText(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps [numberPickerLabel], to calculate the corresponding alpha from the given Translation.
|
||||||
|
*
|
||||||
|
* @param unselectedAlpha The alpha value an unselected element in [TextPicker][org.fknives.android.compose.picker.text.TextPicker] should have.
|
||||||
|
* @param selectedAlpha The alpha value a selected element in [TextPicker][org.fknives.android.compose.picker.text.TextPicker] should have.
|
||||||
|
* @param numberPickerLabel the actual label Composable.
|
||||||
|
* @param modifier additional [Modifier] for [numberPickerLabel]
|
||||||
|
*
|
||||||
|
* As described in [TextPicker][org.fknives.android.compose.picker.text.TextPicker].
|
||||||
|
* translation is the movement of the element:
|
||||||
|
* - 1 -> selected
|
||||||
|
* - 0.5 -> unselected
|
||||||
|
* - 0 -> completely outside of boundaries.
|
||||||
|
* - movement between translation are expected to be proper (such as setting alpha based on the translation linearly).
|
||||||
|
*
|
||||||
|
* This mediator, calculates the linear alpha in the following way:
|
||||||
|
* - 1.0 -> [selectedAlpha] is shown
|
||||||
|
* - 0.75 -> middle way linearly between [selectedAlpha] and [unselectedAlpha]
|
||||||
|
* - 0.5f -> [unselectedAlpha]
|
||||||
|
* - 0.25 -> middle way linearly between [unselectedAlpha] and 0 Alpha.
|
||||||
|
* - 0.0 -> completely gone 0 Alpha.
|
||||||
|
*/
|
||||||
fun defaultNumberPickerTextAlphaModifier(
|
fun defaultNumberPickerTextAlphaModifier(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
unselectedAlpha: Float = TextPickerDefaults.unselectedAlpha,
|
unselectedAlpha: Float = TextPickerDefaults.unselectedAlpha,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,16 @@ import androidx.compose.ui.layout.Placeable
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import org.fknives.android.compose.picker.text.TextPickerState
|
import org.fknives.android.compose.picker.text.TextPickerState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default TextMeasurePolicy of [TextPicker][org.fknives.android.compose.picker.text.TextPicker].
|
||||||
|
*
|
||||||
|
* Intended to be used together with [TextPickerContent] and [DefaultTextPickerContent].
|
||||||
|
*
|
||||||
|
* The expected [numberOfItemsToPlace] items are places one below the other, translated by [state].
|
||||||
|
* Only [numberOfHeightMeasuringItems] items are counted in height, because elements are expected to be translated in and out of boundaries.
|
||||||
|
*
|
||||||
|
* Can be extended via [placementAfterItems] to place additional Items if necessary.
|
||||||
|
*/
|
||||||
open class DefaultTextPickerMeasurePolicy(private val state: TextPickerState) : MeasurePolicy {
|
open class DefaultTextPickerMeasurePolicy(private val state: TextPickerState) : MeasurePolicy {
|
||||||
|
|
||||||
open val numberOfItemsToPlace = 4
|
open val numberOfItemsToPlace = 4
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.Placeable
|
import androidx.compose.ui.layout.Placeable
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import org.fknives.android.compose.picker.text.TextPickerContent
|
|
||||||
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
import org.fknives.android.compose.picker.text.util.TextPickerDefaults
|
||||||
import org.fknives.android.compose.picker.text.TextPickerState
|
import org.fknives.android.compose.picker.text.TextPickerState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default [TextPickerContent] for [LinedTextPicker][org.fknives.android.compose.picker.text.LinedTextPicker].
|
||||||
|
*/
|
||||||
class LinedTextPickerContent(
|
class LinedTextPickerContent(
|
||||||
private val dividerModifier: Modifier = Modifier,
|
private val dividerModifier: Modifier = Modifier,
|
||||||
private val dividerColor: @Composable () -> Color = { MaterialTheme.colors.onSurface.copy(alpha = TextPickerDefaults.dividerAlpha) },
|
private val dividerColor: @Composable () -> Color = { MaterialTheme.colors.onSurface.copy(alpha = TextPickerDefaults.dividerAlpha) },
|
||||||
|
|
@ -18,20 +20,28 @@ class LinedTextPickerContent(
|
||||||
private val dividerStartIndent: Dp = TextPickerDefaults.dividerStartIndent
|
private val dividerStartIndent: Dp = TextPickerDefaults.dividerStartIndent
|
||||||
) : TextPickerContent {
|
) : TextPickerContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the actual content.
|
||||||
|
*
|
||||||
|
* Calculates the indexes around [selected], gets each item's text via [textForIndex].
|
||||||
|
* From that the [item]s are placed with their respective text and given translation.
|
||||||
|
*
|
||||||
|
* Also adds 2 Divider configured by constructor at the end.
|
||||||
|
* The Dividers are expected to be places by [LinedTextPickerMeasurePolicy].
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(
|
override fun Content(
|
||||||
textForIndex: (Int) -> String,
|
textForIndex: (Int) -> String,
|
||||||
selected: Int,
|
selected: Int,
|
||||||
moveUnsafeToProperIndex: (Int) -> Int,
|
|
||||||
before1TranslatePercent: Float,
|
before1TranslatePercent: Float,
|
||||||
itemTranslatePercent: Float,
|
itemTranslatePercent: Float,
|
||||||
after1TranslatePercent: Float,
|
after1TranslatePercent: Float,
|
||||||
after2TranslatePercent: Float,
|
after2TranslatePercent: Float,
|
||||||
item: @Composable (text: String, translation: Float) -> Unit
|
item: @Composable (text: String, translation: Float) -> Unit
|
||||||
) {
|
) {
|
||||||
val before1 = moveUnsafeToProperIndex(selected - 1)
|
val before1 = selected - 1
|
||||||
val after1 = moveUnsafeToProperIndex(selected + 1)
|
val after1 = selected + 1
|
||||||
val after2 = moveUnsafeToProperIndex(selected + 2)
|
val after2 = selected + 2
|
||||||
item(
|
item(
|
||||||
text = textForIndex(before1),
|
text = textForIndex(before1),
|
||||||
translation = before1TranslatePercent
|
translation = before1TranslatePercent
|
||||||
|
|
@ -63,6 +73,12 @@ class LinedTextPickerContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as [DefaultTextPickerMeasurePolicy], with additiona Divider positioning.
|
||||||
|
*
|
||||||
|
* Expected to be used together with [LinedTextPickerContent] providing the Dividers,
|
||||||
|
* which this MeasurePolicy places around the selected Item's usual place.
|
||||||
|
*/
|
||||||
class LinedTextPickerMeasurePolicy(state: TextPickerState) : DefaultTextPickerMeasurePolicy(state = state) {
|
class LinedTextPickerMeasurePolicy(state: TextPickerState) : DefaultTextPickerMeasurePolicy(state = state) {
|
||||||
|
|
||||||
override fun Placeable.PlacementScope.placementAfterItems(placeables: List<Placeable>, state: TextPickerState) {
|
override fun Placeable.PlacementScope.placementAfterItems(placeables: List<Placeable>, state: TextPickerState) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.fknives.android.compose.picker.text.content
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
fun interface TextPickerContent {
|
||||||
|
@Composable
|
||||||
|
fun Content(
|
||||||
|
textForIndex: (Int) -> String,
|
||||||
|
selected: Int,
|
||||||
|
before1TranslatePercent: Float,
|
||||||
|
itemTranslatePercent: Float,
|
||||||
|
after1TranslatePercent: Float,
|
||||||
|
after2TranslatePercent: Float,
|
||||||
|
item: @Composable (text: String, translation: Float) -> Unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,56 @@ package org.fknives.android.compose.picker.text.util
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remembers the lapCounterByIndexOfDifference function with fixed [selectedIndex] and [itemCount].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberLapsCounterByOnIndexDifference(selectedIndex: Int, itemCount: Int) = remember(selectedIndex, itemCount) {
|
||||||
|
createLapsCounterByOnIndexDifference(selectedIndex = selectedIndex, itemCount = itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Curry function for [lapCounterByIndexOfDifference]
|
||||||
|
* Keeps [itemCount] and [selectedIndex] for scope.
|
||||||
|
*
|
||||||
|
* @return Returned lambda expectes the indexDifference for the [lapCounterByIndexOfDifference] calculation.
|
||||||
|
*/
|
||||||
fun createLapsCounterByOnIndexDifference(itemCount: Int, selectedIndex: Int): (Int) -> Int = { indexDifference ->
|
fun createLapsCounterByOnIndexDifference(itemCount: Int, selectedIndex: Int): (Int) -> Int = { indexDifference ->
|
||||||
lapCounterByIndexOfDifference(itemCount = itemCount, indexDifference = indexDifference, selectedIndex = selectedIndex)
|
lapCounterByIndexOfDifference(itemCount = itemCount, indexDifference = indexDifference, selectedIndex = selectedIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function intended to be used with onIndexDifferenceChanging of [TextPicker][org.fknives.android.compose.picker.text.TextPicker].
|
||||||
|
*
|
||||||
|
* Calculates how many times the TextPicker were scrolled over (first element -to-> last element by index decrease or vice versa)
|
||||||
|
* Useful when OverScrolling needs some reaction.
|
||||||
|
*
|
||||||
|
* @param itemCount Number of items
|
||||||
|
* @param selectedIndex the currently selected index, must be between `0 until [itemCount]`
|
||||||
|
* @param indexDifference the current index difference from [selectedIndex].
|
||||||
|
*
|
||||||
|
* @return The Round Around Scrolling.
|
||||||
|
*
|
||||||
|
* Example1:
|
||||||
|
* `itemCount = 3, selectedIndex = 0, indexDifference = -1.`
|
||||||
|
* This results in -1, meaning the currently shown element is index 2. 1 overscroll happened from index 0 to index 2.
|
||||||
|
*
|
||||||
|
* Example2:
|
||||||
|
* `itemCount = 3, selectedIndex = 0, indexDifference = -3.`
|
||||||
|
* This results in -1, meaning the currently shown element is index 0. 1 overscroll happened from index 0 to index 2.
|
||||||
|
*
|
||||||
|
* Example3:
|
||||||
|
* `itemCount = 3, selectedIndex = 0, indexDifference = -4.`
|
||||||
|
* This results in -2, meaning the currently shown element is index 2. 2 overscroll happened from index 0 to index 2.
|
||||||
|
*
|
||||||
|
* Example3:
|
||||||
|
* `itemCount = 3, selectedIndex = 0, indexDifference = 3.`
|
||||||
|
* This results in 1, meaning the currently shown element is index 0. 1 overscroll happened from index 2 to index 0.
|
||||||
|
*
|
||||||
|
* Example3:
|
||||||
|
* `itemCount = 3, selectedIndex = 0, indexDifference = 5.`
|
||||||
|
* This results in 1, meaning the currently shown element is index 2. 1 overscroll happened from index 2 to index 0.
|
||||||
|
*/
|
||||||
fun lapCounterByIndexOfDifference(
|
fun lapCounterByIndexOfDifference(
|
||||||
itemCount: Int,
|
itemCount: Int,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
|
|
@ -29,8 +75,3 @@ fun lapCounterByIndexOfDifference(
|
||||||
|
|
||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberLapsCounterByOnIndexDifference(selectedIndex: Int, itemCount: Int) = remember(selectedIndex, itemCount) {
|
|
||||||
createLapsCounterByOnIndexDifference(selectedIndex = selectedIndex, itemCount = itemCount)
|
|
||||||
}
|
|
||||||
|
|
@ -4,10 +4,24 @@ import org.fknives.android.compose.picker.text.TextPickerState
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolling Offset Limiter intended for [TextPickerAnimator][org.fknives.android.compose.picker.text.TextPickerAnimator].
|
||||||
|
*
|
||||||
|
* Provides to [default]s:
|
||||||
|
* - [NoLimit] Used when scrolling should not be limited
|
||||||
|
* (like RoundAround ([TextPicker][org.fknives.android.compose.picker.text.TextPicker])
|
||||||
|
* - [MinMaxLimit] Used when scrolling should be limited to maximum scroll of up to First Element and down to LastElement.
|
||||||
|
*/
|
||||||
fun interface OffsetLimiter {
|
fun interface OffsetLimiter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hard limit on offset, to ensure indexes are within range.
|
||||||
|
*/
|
||||||
fun limit(offset: Float, state: TextPickerState): Float
|
fun limit(offset: Float, state: TextPickerState): Float
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft limit on offset, to enable overshooting for animations.
|
||||||
|
*/
|
||||||
fun overshootLimit(offset: Float, state: TextPickerState): Float =
|
fun overshootLimit(offset: Float, state: TextPickerState): Float =
|
||||||
limit(offset, state)
|
limit(offset, state)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,19 @@ package org.fknives.android.compose.picker.text.util
|
||||||
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for [TextPicker][org.fknives.android.compose.picker.text.TextPicker]
|
||||||
|
*/
|
||||||
object TextPickerDefaults {
|
object TextPickerDefaults {
|
||||||
const val selected = 0
|
const val selected = 0
|
||||||
const val velocityMultiplier = 0.3f
|
const val velocityMultiplier = 0.3f
|
||||||
const val unselectedAlpha = 0.6f
|
const val unselectedAlpha = 0.6f
|
||||||
const val selectedAlpha = 1f
|
const val selectedAlpha = 1f
|
||||||
|
/**
|
||||||
|
* [TextPickerAnimator][org.fknives.android.compose.picker.text.TextPickerAnimator] checks against this to not emit unnecessary updates.
|
||||||
|
*/
|
||||||
internal val onIndexDifferenceChanging: (Int) -> Unit = {}
|
internal val onIndexDifferenceChanging: (Int) -> Unit = {}
|
||||||
|
|
||||||
const val roundAround = true
|
const val roundAround = true
|
||||||
const val dividerAlpha = 0.12f
|
const val dividerAlpha = 0.12f
|
||||||
val dividerThickness = 2.dp
|
val dividerThickness = 2.dp
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import androidx.compose.ui.unit.Density
|
||||||
import org.fknives.android.compose.picker.text.TextPickerAnimator
|
import org.fknives.android.compose.picker.text.TextPickerAnimator
|
||||||
import org.fknives.android.compose.picker.text.TextPickerState
|
import org.fknives.android.compose.picker.text.TextPickerState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function caching [TextPickerState]
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberTextPickerState(selected: Int, itemCount: Int): TextPickerState {
|
fun rememberTextPickerState(selected: Int, itemCount: Int): TextPickerState {
|
||||||
require(selected >= 0) { "Selected value ($selected) is less than 0!" }
|
require(selected >= 0) { "Selected value ($selected) is less than 0!" }
|
||||||
|
|
@ -26,6 +29,9 @@ fun rememberTextPickerState(selected: Int, itemCount: Int): TextPickerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function caching [TextPickerAnimator]
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberTextPickerAnimator(
|
fun rememberTextPickerAnimator(
|
||||||
roundAround: Boolean,
|
roundAround: Boolean,
|
||||||
|
|
@ -43,6 +49,9 @@ fun rememberTextPickerAnimator(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function caching [TextPickerAnimator]
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberTextPickerAnimator(
|
fun rememberTextPickerAnimator(
|
||||||
offsetLimiter: OffsetLimiter,
|
offsetLimiter: OffsetLimiter,
|
||||||
|
|
@ -61,6 +70,12 @@ fun rememberTextPickerAnimator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Lambda changing index based on [roundAround].
|
||||||
|
*
|
||||||
|
* If roundAround is False, means the index is not touched, since it should not behave like a wheel.
|
||||||
|
* IF roundAround is True, means the index is feed into [moveUnsafeToProperIndex] to behave like a wheel.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberDefaultMoveUnsafeToProperIndex(itemCount: Int, roundAround: Boolean) =
|
fun rememberDefaultMoveUnsafeToProperIndex(itemCount: Int, roundAround: Boolean) =
|
||||||
if (roundAround) {
|
if (roundAround) {
|
||||||
|
|
@ -69,12 +84,21 @@ fun rememberDefaultMoveUnsafeToProperIndex(itemCount: Int, roundAround: Boolean)
|
||||||
remember { { index: Int -> index } }
|
remember { { index: Int -> index } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Curry function for [moveUnsafeToProperIndex] which stores the [itemCount]
|
||||||
|
*
|
||||||
|
* @return lambda expecting the unsafeIndex to call [moveUnsafeToProperIndex]
|
||||||
|
*/
|
||||||
fun createMoveUnsafeToProperIndex(itemCount: Int) = moveUnsafeToProperIndex@{ unsafeIndex: Int ->
|
fun createMoveUnsafeToProperIndex(itemCount: Int) = moveUnsafeToProperIndex@{ unsafeIndex: Int ->
|
||||||
moveUnsafeToProperIndex(unsafeIndex, itemCount)
|
moveUnsafeToProperIndex(unsafeIndex, itemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int) : Int {
|
/**
|
||||||
|
* Moves a given [unsafeIndex] between `0 until [itemCount]` like it is a continuous wheel.
|
||||||
|
* Meaning that `unsafeIndex=-1` becomes `itemCount-1` while `unsafeIndex=itemCount` becomes 0.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int): Int {
|
||||||
var index = unsafeIndex
|
var index = unsafeIndex
|
||||||
while (index < 0) {
|
while (index < 0) {
|
||||||
index += itemCount
|
index += itemCount
|
||||||
|
|
@ -85,13 +109,42 @@ fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int) : Int {
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches function accessing text for given index safely.
|
||||||
|
* [roundAround] True means the Index will be modified if it needs to be to fall between the safe range for [textForIndex]
|
||||||
|
* [roundAround] False means the if the Index is falls outside of safeZone, [textForUnsafeIndex] will be used as fallback.
|
||||||
|
* Note: unsafeIndex means the index NOT between `0 until itemCount`.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberWrappedTextForIndex(itemCount: Int, roundAround: Boolean, textForIndex: (Int) -> String): (Int) -> String =
|
fun rememberSafeTextForIndex(
|
||||||
|
textForUnsafeIndex: String = "",
|
||||||
|
itemCount: Int,
|
||||||
|
roundAround: Boolean,
|
||||||
|
textForIndex: (Int) -> String,
|
||||||
|
moveUnsafeToProperIndex: (Int) -> Int
|
||||||
|
): (Int) -> String =
|
||||||
if (roundAround) {
|
if (roundAround) {
|
||||||
textForIndex
|
remember(moveUnsafeToProperIndex, textForIndex) {
|
||||||
|
createSafeRoundAroundTextForIndex(moveUnsafeToProperIndex = moveUnsafeToProperIndex, textForIndex = textForIndex)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
remember(itemCount, textForIndex) {
|
remember(itemCount, textForIndex) {
|
||||||
{ index -> if (index < 0 || index >= itemCount) "" else textForIndex(index) }
|
createSafeOrDefaultTextForIndex(itemCount = itemCount, textForIndex = textForIndex, textForUnsafeIndex = textForUnsafeIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns lambda, which ensures the index is moved to into safe position by [moveUnsafeToProperIndex] before accessing [textForIndex]
|
||||||
|
*/
|
||||||
|
fun createSafeRoundAroundTextForIndex(moveUnsafeToProperIndex: (Int) -> Int, textForIndex: (Int) -> String): (Int) -> String =
|
||||||
|
{ index: Int ->
|
||||||
|
textForIndex(moveUnsafeToProperIndex(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns lambda, which ensures the index is between `0 until itemCount` before accessing [textForIndex], fallbacks [textForUnsafeIndex]
|
||||||
|
*/
|
||||||
|
fun createSafeOrDefaultTextForIndex(textForUnsafeIndex: String = "", itemCount: Int, textForIndex: (Int) -> String): (Int) -> String =
|
||||||
|
{ index: Int ->
|
||||||
|
if (index in 0 until itemCount) textForIndex(index) else textForUnsafeIndex
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue