From bd377bef00b8d0b60865d9da43d190b1de020138 Mon Sep 17 00:00:00 2001
From: jayChou <549226148@qq.com>
Date: Mon, 9 May 2022 09:22:59 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9ESwipe?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 7 +
app/src/main/AndroidManifest.xml | 11 ++
.../cui/testclient/swipe/SwipeActivity.kt | 38 ++++
.../idesign/cui/testclient/swipe/SwipeTest.kt | 182 ++++++++++++++++++
.../main/java/cn/idesign/cui/swipe/Swipe.kt | 169 ++++++++++++++++
.../java/cn/idesign/cui/swipe/SwipeCopy.kt | 112 +++++++++++
6 files changed, 519 insertions(+)
create mode 100644 app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeActivity.kt
create mode 100644 app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeTest.kt
create mode 100644 cui/src/main/java/cn/idesign/cui/swipe/Swipe.kt
create mode 100644 cui/src/main/java/cn/idesign/cui/swipe/SwipeCopy.kt
diff --git a/README.md b/README.md
index 9d613fd..13b3a1a 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@
- [StatefulLayout](#StatefulLayout)
- [Stepper](#Stepper)
- [Steps](#Steps)
+- [Swipe](#Swipe)
- [TimeSelect](#TimeSelect)
- [VerifyCode](#VerifyCode)
@@ -172,6 +173,12 @@ Name | 截图
--- | ---
Steps|
+Swipe
+======================
+Name | 截图
+--- | ---
+Swipe|
+
TimeSelect
======================
Name | 截图
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 69d73e6..626f712 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -296,6 +296,17 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeActivity.kt b/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeActivity.kt
new file mode 100644
index 0000000..b6551fe
--- /dev/null
+++ b/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeActivity.kt
@@ -0,0 +1,38 @@
+package cn.idesign.cui.testclient.swipe
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.ui.Modifier
+import cn.idesign.cui.testclient.ui.theme.CUITestTheme
+
+class SwipeActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ CUITestTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = {
+ Text(
+ text = "Swipe示例",
+ color = MaterialTheme.colors.onPrimary
+ )
+ },
+ backgroundColor = MaterialTheme.colors.primary,
+ )
+ },
+ modifier = Modifier.fillMaxSize()
+ ) {
+ SwipeTest()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeTest.kt b/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeTest.kt
new file mode 100644
index 0000000..92e61af
--- /dev/null
+++ b/app/src/main/java/cn/idesign/cui/testclient/swipe/SwipeTest.kt
@@ -0,0 +1,182 @@
+package cn.idesign.cui.testclient.swipe
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.ContentAlpha
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import cn.idesign.cui.cell.Cell
+import cn.idesign.cui.swipe.Swipe
+import cn.idesign.cui.swipe.SwipeDirection
+import cn.idesign.cui.swipe.rememberSwipeState
+
+@Composable
+fun SwipeTest() {
+ var checked by remember {
+ mutableStateOf(false)
+ }
+ val state = rememberSwipeState()
+ LazyColumn(
+ Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ item {
+ Text(
+ text = "基本用法",
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ color = MaterialTheme.colors.onSurface.copy(ContentAlpha.high),
+ style = MaterialTheme.typography.subtitle1.copy(fontWeight = FontWeight.Medium)
+ )
+ Swipe(background = {
+ Box(
+ modifier = Modifier
+ .width(66.dp)
+ .fillMaxHeight()
+ .background(Color.Red),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "删除",
+ color = Color.White,
+
+ )
+ }
+ }) {
+ Cell(text = "左滑删除")
+ }
+ }
+ item {
+ Text(
+ text = "右滑删除",
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ color = MaterialTheme.colors.onSurface.copy(ContentAlpha.high),
+ style = MaterialTheme.typography.subtitle1.copy(fontWeight = FontWeight.Medium)
+ )
+ Swipe(
+ direction = SwipeDirection.LeftToRight,
+ background = {
+ Box(
+ modifier = Modifier
+ .width(66.dp)
+ .fillMaxHeight()
+ .background(MaterialTheme.colors.primary),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "收藏",
+ color = Color.White,
+
+ )
+ }
+ }) {
+ Cell(text = "右滑删除")
+ }
+ }
+ item {
+ Text(
+ text = "多个操作",
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ color = MaterialTheme.colors.onSurface.copy(ContentAlpha.high),
+ style = MaterialTheme.typography.subtitle1.copy(fontWeight = FontWeight.Medium)
+ )
+ Swipe(background = {
+ Row {
+ Box(
+ modifier = Modifier
+ .width(66.dp)
+ .fillMaxHeight()
+ .background(Color.Red),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "删除",
+ color = Color.White,
+
+ )
+ }
+ Box(
+ modifier = Modifier
+ .width(66.dp)
+ .fillMaxHeight()
+ .background(MaterialTheme.colors.primary),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "收藏",
+ color = Color.White,
+
+ )
+ }
+ }
+ }) {
+ Cell(text = "多个操作")
+ }
+ }
+
+ item {
+ Text(
+ text = "异步控制",
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ color = MaterialTheme.colors.onSurface.copy(ContentAlpha.high),
+ style = MaterialTheme.typography.subtitle1.copy(fontWeight = FontWeight.Medium)
+ )
+ Swipe(
+ state = state,
+ background = {
+ Box(
+ modifier = Modifier
+ .width(66.dp)
+ .fillMaxHeight()
+ .background(Color.Red),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "删除",
+ color = Color.White,
+
+ )
+ }
+
+ }) {
+ Cell(
+ text = "多个操作",
+ rightComponent = {
+ Switch(checked = checked, onCheckedChange = {
+ checked = it
+ if (it) {
+ state.open()
+ } else {
+ state.close()
+ }
+ })
+ },
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/cui/src/main/java/cn/idesign/cui/swipe/Swipe.kt b/cui/src/main/java/cn/idesign/cui/swipe/Swipe.kt
new file mode 100644
index 0000000..7a7332c
--- /dev/null
+++ b/cui/src/main/java/cn/idesign/cui/swipe/Swipe.kt
@@ -0,0 +1,169 @@
+package cn.idesign.cui.swipe
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.FractionalThreshold
+import androidx.compose.material.rememberSwipeableState
+import androidx.compose.material.swipeable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
+import kotlinx.coroutines.launch
+import kotlin.math.roundToInt
+
+@SuppressLint("RememberReturnType")
+@OptIn(ExperimentalComposeUiApi::class, androidx.compose.material.ExperimentalMaterialApi::class)
+@Composable
+fun Swipe(
+ state: SwipeState = rememberSwipeState(),
+ threshold: Float = 0.3f,
+ direction: SwipeDirection = SwipeDirection.RightToLeft,
+ background: @Composable () -> Unit,
+ content: @Composable () -> Unit,
+) {
+ var boxWidthPx by remember {
+ mutableStateOf(0)
+ }
+ var boxHeightPx by remember {
+ mutableStateOf(0)
+ }
+ var backgroundWidthPx by remember {
+ mutableStateOf(0)
+ }
+ val scope = rememberCoroutineScope()
+ val swipeableState = rememberSwipeableState(0)
+ val anchors by remember(backgroundWidthPx, direction) {
+ if (direction == SwipeDirection.RightToLeft) {
+ mutableStateOf(mapOf(0f to 0, -backgroundWidthPx.toFloat() to 1))
+ } else {
+ mutableStateOf(mapOf(0f to 0, backgroundWidthPx.toFloat() to 1))
+ }
+ }
+
+ remember(state.currentValue) {
+ when (state.currentValue) {
+ SwipeValue.Hidden -> scope.launch {
+ swipeableState.animateTo(0)
+ }
+ SwipeValue.Open -> scope.launch { swipeableState.animateTo(1) }
+ }
+
+ }
+
+
+ val swipeModifier = if (backgroundWidthPx > 0) Modifier.swipeable(
+ state = swipeableState,
+ anchors = anchors,
+ thresholds = { _, _ -> FractionalThreshold(threshold) },
+ orientation = Orientation.Horizontal,
+ ) else Modifier
+ Box(modifier = Modifier
+ .onSizeChanged {
+ boxWidthPx = it.width
+ boxHeightPx = it.height
+ }
+ .clipToBounds()
+ .then(swipeModifier)
+ ) {
+
+ val backgroundOffsetX = when (direction) {
+ SwipeDirection.LeftToRight -> -backgroundWidthPx + swipeableState.offset.value.roundToInt()
+ SwipeDirection.RightToLeft -> boxWidthPx + swipeableState.offset.value.roundToInt()
+ }
+ Box(modifier = Modifier
+ .fillMaxWidth()
+ .offset {
+ IntOffset(
+ x = swipeableState.offset.value.roundToInt(),
+ y = 0
+ )
+ }) {
+ content()
+ }
+ Box(modifier = Modifier
+ .height(with(LocalDensity.current) { boxHeightPx.toDp() })
+ .onSizeChanged {
+ backgroundWidthPx = it.width
+ }
+ .offset {
+ IntOffset(
+ x = backgroundOffsetX,
+ y = 0
+ )
+ }
+ ) {
+ background()
+ }
+
+ }
+}
+
+sealed class SwipeDirection {
+ object LeftToRight : SwipeDirection()
+ object RightToLeft : SwipeDirection()
+}
+
+enum class SwipeValue {
+ Hidden,
+ Open,
+}
+
+
+@Composable
+fun rememberSwipeState(
+ initialValue: SwipeValue = SwipeValue.Hidden
+): SwipeState = rememberSaveable(saver = SwipeState.SAVER) {
+ SwipeState(
+ initialValue = initialValue,
+ )
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+class SwipeState(
+ val initialValue: SwipeValue,
+) {
+
+ private var _currentValue: SwipeValue by mutableStateOf(initialValue)
+
+ val currentValue: SwipeValue
+ get() = _currentValue
+
+ fun open() {
+ if (_currentValue != SwipeValue.Open) {
+ _currentValue = SwipeValue.Open
+ }
+ }
+
+ fun close() {
+ if (_currentValue != SwipeValue.Hidden) {
+ _currentValue = SwipeValue.Hidden
+ }
+ }
+
+ companion object {
+ val SAVER: Saver = Saver(
+ save = {
+ it.initialValue
+ },
+ restore = {
+ SwipeState(it)
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/cui/src/main/java/cn/idesign/cui/swipe/SwipeCopy.kt b/cui/src/main/java/cn/idesign/cui/swipe/SwipeCopy.kt
new file mode 100644
index 0000000..ffc0837
--- /dev/null
+++ b/cui/src/main/java/cn/idesign/cui/swipe/SwipeCopy.kt
@@ -0,0 +1,112 @@
+package cn.idesign.cui.swipe
+
+import android.util.Log
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.gestures.detectHorizontalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
+import kotlin.math.abs
+import kotlin.math.roundToInt
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun SwipeCopy(
+ threshold: Float = 0.5f,
+ background: @Composable () -> Unit,
+ content: @Composable () -> Unit,
+) {
+ var boxWidthPx by remember {
+ mutableStateOf(0)
+ }
+ var boxHeightPx by remember {
+ mutableStateOf(0)
+ }
+ var backgroundWidthPx by remember {
+ mutableStateOf(0)
+ }
+ var needConsume by remember {
+ mutableStateOf(0f)
+ }
+ val animateOffset by animateFloatAsState(needConsume)
+
+ var offsetX by remember() { mutableStateOf(0f) }
+ Box(modifier = Modifier
+ .onSizeChanged {
+ boxWidthPx = it.width
+ boxHeightPx = it.height
+ Log.d("Swipe", "boxWidthPx:${boxWidthPx}")
+ }
+ .clipToBounds()
+
+ .pointerInput(Unit) {
+ detectHorizontalDragGestures(
+ onDragEnd = {
+ //计算当前offsetX 是否在阀值
+ val offsetXPercent = abs(offsetX) / backgroundWidthPx
+ if (offsetXPercent == 1f) {
+ //已经打开
+ needConsume = 0f
+ } else if (offsetXPercent == 0f) {
+ //已经关闭
+ needConsume = 0f
+ } else if (offsetXPercent >= threshold) {
+ //需要打开
+ needConsume = (-(backgroundWidthPx + offsetX))
+ } else {
+ //需要关闭
+ needConsume = -offsetX
+ }
+ Log.d("Swipe", "offsetXPercent:${offsetXPercent}")
+ },
+ onDragCancel = {},
+ onDragStart = {},
+ onHorizontalDrag = { change, dragAmount ->
+ offsetX = (offsetX + dragAmount).coerceIn(-backgroundWidthPx.toFloat(), 0f)
+ val offset = change.position.x.coerceIn(0f, boxWidthPx.toFloat())
+ Log.d("Swipe", "offset:${offset},offsetX:${offsetX}")
+ },
+ )
+ }
+ ) {
+
+
+ Box(modifier = Modifier
+ .fillMaxWidth()
+ .offset {
+ IntOffset(
+ x = offsetX.roundToInt() + animateOffset.roundToInt(),
+ y = 0
+ )
+ }) {
+ content()
+ }
+ Box(modifier = Modifier
+ .height(with(LocalDensity.current) { boxHeightPx.toDp() })
+ .onSizeChanged {
+ backgroundWidthPx = it.width
+ Log.d("Swipe", "backgroundWidthPx:${backgroundWidthPx},height:${it.height}")
+ }
+ .offset {
+ IntOffset(
+ x = boxWidthPx + offsetX.roundToInt() + animateOffset.roundToInt(),
+ y = 0
+ )
+ }) {
+ background()
+ }
+ }
+}
\ No newline at end of file