Jelajahi Sumber

新增可缩放和移动的布局,用于显示工作流和编辑工作流操作

bjb 5 hari lalu
induk
melakukan
61b1131c66

+ 1 - 0
app/build.gradle.kts

@@ -63,6 +63,7 @@ dependencies {
     implementation(libs.push)
     implementation(libs.push.third)
     implementation(libs.push.third.xiaomi)
+    implementation(libs.androidx.ui)
 
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)

+ 7 - 2
app/src/main/AndroidManifest.xml

@@ -30,14 +30,19 @@
         <!--  登录页面  -->
         <activity
             android:name=".ui.pages.login.PageLogin"
-            android:screenOrientation="portrait"
-            android:exported="true" />
+            android:exported="true"
+            android:screenOrientation="portrait" />
         <!--  主页面  -->
         <activity
             android:name=".ui.pages.home.PageHome"
             android:exported="true"
             android:launchMode="singleTask"
             android:screenOrientation="portrait" />
+        <!--  编辑流程  -->
+        <activity
+            android:name=".ui.pages.edit.step.PageEditStep"
+            android:exported="true"
+            android:screenOrientation="portrait" />
         <!--  阿里消息推送服务配置  -->
         <service
             android:name=".service.AliPushService"

+ 5 - 6
app/src/main/java/com/iscs/bozzys/ui/pages/PageSplash.kt

@@ -6,7 +6,6 @@ import android.graphics.Color
 import android.os.Build
 import android.util.Log
 import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
@@ -27,9 +26,8 @@ import com.alibaba.sdk.android.push.noonesdk.PushServiceFactory
 import com.iscs.bozzys.R
 import com.iscs.bozzys.service.AliPushService
 import com.iscs.bozzys.ui.base.PageBase
-import com.iscs.bozzys.ui.pages.home.openPageHome
+import com.iscs.bozzys.ui.pages.edit.step.openPageEditStep
 import com.iscs.bozzys.ui.pages.login.openPageLogin
-import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.utils.Storage
 import kotlinx.coroutines.delay
 
@@ -45,6 +43,7 @@ class PageSplash : PageBase() {
             Box(
                 Modifier
                     .clip(RoundedCornerShape(20))
+
             ) {
                 Image(
                     painter = painterResource(R.mipmap.logo),
@@ -61,7 +60,7 @@ class PageSplash : PageBase() {
             // 做SDK的初始化操作,有一些没必要在Application中初始化操作的,在这里进行初始化操作
             initSDK()
             delay(3000)
-            if (Storage.isLogin()) openPageHome() else openPageLogin()
+            if (Storage.isLogin()) openPageEditStep() else openPageLogin()
             destroyDelay(1000)
         }
     }
@@ -89,12 +88,12 @@ class PageSplash : PageBase() {
             override fun onSuccess(msg: String?) {
                 // 消息推送初始化成功
                 // 这里可能要告知服务端当前推送的设备id,便于后续设备推送通知
-                Log.i("MPS","消息推送注册成功")
+                Log.i("MPS", "消息推送注册成功")
             }
 
             override fun onFailed(errCode: String?, errMsg: String?) {
                 // 消息推送初始化失败
-                Log.i("MPS","消息推送注册失败:$errMsg $errCode")
+                Log.i("MPS", "消息推送注册失败:$errMsg $errCode")
             }
 
         })

+ 65 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/PageEditStep.kt

@@ -0,0 +1,65 @@
+package com.iscs.bozzys.ui.pages.edit.step
+
+import android.content.Context
+import android.content.Intent
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import com.iscs.bozzys.ui.base.PageBase
+import com.iscs.bozzys.ui.pages.edit.step.compose.ConnectionLayer
+import com.iscs.bozzys.ui.pages.edit.step.compose.Node
+import com.iscs.bozzys.ui.pages.edit.step.compose.NodeItem
+import com.iscs.bozzys.ui.pages.edit.step.compose.ZoomPanContainer
+
+/**
+ * 跳转到编辑流程页面
+ */
+fun Context.openPageEditStep() {
+    startActivity(Intent(this, PageEditStep::class.java))
+}
+
+class PageEditStep : PageBase() {
+    @Composable
+    override fun GetViews(pv: PaddingValues) {
+        val nodes = remember { mutableStateMapOf<String, Node>() }
+        val lines = listOf("A" to "B", "A" to "D", "B" to "C")
+        val ready by remember { derivedStateOf { nodes.isNotEmpty() } }
+        LaunchedEffect(Unit) {
+            nodes["A"] = Node("A", Offset(50f, 0f), Size(80))
+            nodes["B"] = Node("B", Offset(250f, 0f), Size(80))
+            nodes["B"] = Node("D", Offset(250f, 0f), Size(80))
+            nodes["C"] = Node("C", Offset(450f, 0f), Size(80))
+        }
+        ZoomPanContainer(modifier = Modifier.fillMaxSize()) {
+            // 连线操作
+            if (ready) ConnectionLayer(nodes, lines)
+            // 控件显示
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .zIndex(1f),
+                contentAlignment = Alignment.CenterStart
+            ) {
+                NodeItem("A", nodes, Modifier.offset(50.dp, 0.dp))
+                NodeItem("B", nodes, Modifier.offset(250.dp, 0.dp))
+                NodeItem("D", nodes, Modifier.offset(250.dp, 100.dp))
+                NodeItem("C", nodes, Modifier.offset(450.dp, 0.dp))
+            }
+        }
+    }
+
+
+}

+ 117 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/Node.kt

@@ -0,0 +1,117 @@
+package com.iscs.bozzys.ui.pages.edit.step.compose
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.zIndex
+
+/**
+ * 布局之间的连线操作(贝塞尔曲线)
+ */
+@Composable
+fun ConnectionLayer(
+    nodes: Map<String, Node>,
+    connections: List<Pair<String, String>>
+) {
+    Canvas(
+        modifier = Modifier
+            .fillMaxSize()
+            .zIndex(0f)
+    ) {
+        connections.forEach { (fromId, toId) ->
+            val from = nodes[fromId]
+            val to = nodes[toId]
+            if (from != null && to != null) {
+
+                val start = from.rightCenter()
+                val end = to.leftCenter()
+
+                val control1 = Offset(
+                    start.x + 100f,
+                    start.y
+                )
+                val control2 = Offset(
+                    end.x - 100f,
+                    end.y
+                )
+
+                val path = Path().apply {
+                    moveTo(start.x, start.y)
+                    cubicTo(
+                        control1.x, control1.y,
+                        control2.x, control2.y,
+                        end.x, end.y
+                    )
+                }
+
+                drawPath(
+                    path = path,
+                    color = Color.Gray,
+                    style = Stroke(width = 3f)
+                )
+            }
+        }
+    }
+}
+
+/**
+ * 各节点
+ */
+@Composable
+fun NodeItem(
+    id: String,
+    nodes: SnapshotStateMap<String, Node>,
+    modifier: Modifier = Modifier
+) {
+    Box(
+        modifier = modifier
+            .size(80.dp)
+            .background(Color.White, RoundedCornerShape(8.dp))
+            .border(1.dp, Color.Gray)
+            .onGloballyPositioned { coords ->
+                nodes[id] = Node(id = id, offset = coords.positionInParent(), size = coords.size.toSize())
+            },
+        contentAlignment = Alignment.Center
+    ) {
+        Text(id)
+    }
+}
+
+/**
+ * 右边连线
+ */
+fun Node.rightCenter(): Offset =
+    Offset(offset.x + size.width, offset.y + size.height / 2)
+
+/**
+ * 左边连线
+ */
+fun Node.leftCenter(): Offset =
+    Offset(offset.x, offset.y + size.height / 2)
+
+/**
+ * 节点定义
+ */
+data class Node(
+    val id: String,
+    val offset: Offset,
+    val size: Size
+)

+ 108 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/ZoomContainer.kt

@@ -0,0 +1,108 @@
+package com.iscs.bozzys.ui.pages.edit.step.compose
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.rememberTransformableState
+import androidx.compose.foundation.gestures.transformable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import kotlin.math.ceil
+import kotlin.math.floor
+
+/**
+ * 可缩放和移动操作
+ */
+@Composable
+fun ZoomPanContainer(
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.() -> Unit
+) {
+    var scale by remember { mutableFloatStateOf(1f) }
+    var offset by remember { mutableStateOf(Offset.Zero) }
+
+    val transformState = rememberTransformableState { zoomChange, panChange, _ ->
+        scale = (scale * zoomChange).coerceIn(0.5f, 5f)
+        offset += panChange
+    }
+
+    Box(
+        modifier = modifier
+            .clipToBounds()
+            .background(Color(0xFFF8F9FA))
+            .transformable(transformState)
+    ) {
+        // 可缩放和拖拽布局
+        Box(
+            modifier = Modifier.graphicsLayer {
+                transformOrigin = TransformOrigin(0f, 0f)
+                scaleX = scale
+                scaleY = scale
+                translationX = offset.x
+                translationY = offset.y
+            }
+        ) {
+            // 点阵背景
+            InfiniteDotBackground(scale = scale, offset = offset)
+            // 外部自定义子控件
+            content()
+        }
+    }
+}
+
+/**
+ * 缩放画布底下增加背景圆点
+ */
+@Composable
+fun InfiniteDotBackground(
+    scale: Float,
+    offset: Offset,
+    gap: Float = 48f,
+    radius: Float = 4f,
+    color: Color = Color(0xFFD1D1D1)
+) {
+    Canvas(modifier = Modifier.fillMaxSize()) {
+
+        // 屏幕 → 画布坐标
+        val left = -offset.x / scale
+        val top = -offset.y / scale
+        val right = left + size.width / scale
+        val bottom = top + size.height / scale
+
+        // 🔑 关键:外扩范围(至少 2~3 个 gap)
+        val padding = gap * 3
+
+        val startX = floor((left - padding) / gap) * gap
+        val startY = floor((top - padding) / gap) * gap
+        val endX = ceil((right + padding) / gap) * gap
+        val endY = ceil((bottom + padding) / gap) * gap
+
+        var x = startX
+        while (x <= endX) {
+            var y = startY
+            while (y <= endY) {
+                drawCircle(
+                    color = color,
+                    radius = radius,
+                    center = Offset(x, y)
+                )
+                y += gap
+            }
+            x += gap
+        }
+    }
+}
+
+

+ 3 - 2
app/src/main/java/com/iscs/bozzys/ui/pages/home/HomeCompose.kt

@@ -25,6 +25,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.pulltorefresh.PullToRefreshBox
 import androidx.compose.runtime.Composable
@@ -56,7 +57,7 @@ fun HomeCompose(pv: PaddingValues, zIndex: Float, vmHome: VMHome) {
         modifier = Modifier
             .fillMaxSize()
             .zIndex(zIndex)
-            .background(Color.White)
+            .background(MaterialTheme.colorScheme.background)
     ) {
         // 顶部工具栏
         TopToolBar(pv, vmHome)
@@ -154,7 +155,7 @@ private fun TODO(vmHome: VMHome) {
     Column(
         Modifier
             .fillMaxWidth()
-            .background(Color.White)
+            .background(MaterialTheme.colorScheme.background)
             .padding(top = 3.dp)
     ) {
         Text(

+ 4 - 1
app/src/main/java/com/iscs/bozzys/ui/theme/Theme.kt

@@ -13,6 +13,7 @@ import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
 import androidx.core.view.WindowInsetsControllerCompat
@@ -22,13 +23,15 @@ private val DarkColorScheme = darkColorScheme(
     primary = Main,
     secondary = Main,
     tertiary = Main,
+    background = Color.Black
 )
 
 // 标准主题配置
 private val LightColorScheme = lightColorScheme(
     primary = Main,
     secondary = Main,
-    tertiary = Main
+    tertiary = Main,
+    background = Color.White
 )
 
 @OptIn(ExperimentalFoundationApi::class)

TEMPAT SAMPAH
app/src/main/res/mipmap-xhdpi/logo.png


+ 1 - 0
app/src/main/res/values/colors.xml

@@ -7,4 +7,5 @@
     <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
+    <color name="black_alpha_0">#00000000</color>
 </resources>

+ 1 - 1
app/src/main/res/values/themes.xml

@@ -8,7 +8,7 @@
         <item name="android:windowTranslucentNavigation">true</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:windowFullscreen">true</item>
-        <item name="android:background">@color/white</item>
+        <item name="android:background">@color/black_alpha_0</item>
     </style>
 
 </resources>

+ 2 - 0
gradle/libs.versions.toml

@@ -10,6 +10,7 @@ activityCompose = "1.8.0"
 composeBom = "2024.09.00"
 coroutines = "1.6.4"
 push = "3.10.1"
+ui = "1.10.0"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -42,6 +43,7 @@ tencent-mmkv = { group = "com.tencent", name = "mmkv", version = "2.2.3" }
 push = { group = "com.aliyun.ams", name = "alicloud-android-push", version.ref = "push" }
 push_third = { group = "com.aliyun.ams", name = "alicloud-android-third-push", version.ref = "push" }
 push_third_xiaomi = { group = "com.aliyun.ams", name = "alicloud-android-third-push-xiaomi", version.ref = "push" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }