Kotlin Compose 终结toDo项目 点击可以编辑修改todo

Posted 安果移不动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin Compose 终结toDo项目 点击可以编辑修改todo相关的知识,希望对你有一定的参考价值。

Kotlin Compose 完善toDo项目 Surface 渲染背景 与阴影_安果移不动的博客-CSDN博客

TodoComponents.kt
package com.anguomob.jecpack.activity.compose.todo.one

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.*
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anguomob.jecpack.activity.compose.todo.bean.ToDoIcon
import com.anguomob.jecpack.activity.compose.todo.bean.TodoItem

//当item选中的时候 会弹出一个输入框 用于编辑选中ToDoItem的信息
@Composable
fun TodoItemInLineEditor(
    item: TodoItem,
    onEditItemChange: (TodoItem) -> Unit,
    onEditDone: () -> Unit,
    onRemoveItem: () -> Unit,
) 
    TodoItemInput(
        text = item.task,
        onTextChange =  onEditItemChange(item.copy(task = it)) ,
        icon = item.icon,
        onIconChange =  onEditItemChange(item.copy(icon = it)) ,
        iconsVisible = true,
        submit = onEditDone,
        buttonSlot = 
            //保存和删除两个图标
            Row() 
                val shrinkButtons = Modifier.widthIn(20.dp)
                TextButton(onClick = onEditDone, modifier = shrinkButtons) 
                    //文字的emoji表情
                    Text(
                        text = "\\uD83D\\uDCBE",
                        textAlign = TextAlign.End,
                    )

                

                TextButton(onClick = onRemoveItem, modifier = shrinkButtons) 
                    //文字的emoji表情
                    Text(
                        text = "删除",
                        textAlign = TextAlign.End,
                    )

                
            
        
    )


@Composable
fun ToDoItemInputBackground(
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit,
    elevatioon: Boolean
) 
    //帧动画形式展示Surface底部的阴影
    val animatedElevation by animateDpAsState(
        targetValue = if (elevatioon) 1.dp else 0.dp,
        TweenSpec(300)
    )
    Surface(
        color = MaterialTheme.colors.onSurface.copy(alpha = 0.05f),
        shape = RectangleShape,
        elevation = animatedElevation
    ) 
        Row(modifier = modifier.animateContentSize(animationSpec = TweenSpec(300))) 
            content()
        
    


//输入框
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun TodoInputText(
    text: String,
    onTextChanged: (String) -> Unit,
    onImeAction: () -> Unit,
    modifier: Modifier = Modifier,
) 

    val keyboardController = LocalSoftwareKeyboardController.current
    TextField(
        value = text,
        onValueChange = onTextChanged,
        modifier = modifier,
        colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
        maxLines = 1,
        //配置软键盘
        keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
        keyboardActions = KeyboardActions(onDone = 
            onImeAction()
            //点击完成之后 隐藏键盘
            keyboardController?.hide()
        )
    )


@Composable()
fun TodoEditButton(
    onClick: () -> Unit,
    text: String,
    modifier: Modifier = Modifier,
    enable: Boolean = true
) 
    TextButton(
        onClick = onClick,
        shape = CircleShape,
        colors = ButtonDefaults.buttonColors(),
        modifier = modifier,
        enabled = enable
    ) 
        Text(text)
    


//顶部布局

@Composable
fun TodoItemEntryInput(onItemComplete: (TodoItem) -> Unit) 
    val (text, setText) = remember 
        mutableStateOf("")
    

    val (icon, setIcon) = remember 
        mutableStateOf(ToDoIcon.Default)
    
    //icon是否可一件取决于文本是否有内容
    val iconsVisible = text.isNotBlank()
    //点击add
    val submit = 
        onItemComplete(TodoItem(text, icon))
        setText("")
        setIcon(ToDoIcon.Default)
    

    TodoItemInput(
        text = text,
        onTextChange = setText,
        icon = icon,
        onIconChange = setIcon,
        iconsVisible = iconsVisible,
        submit = submit,
        buttonSlot = 
            TodoEditButton(
                onClick = submit,
                text = "添加",
                enable = text.isNotBlank()
            )
        
    )



//输入框 加其他
@Composable
fun TodoItemInput(
    text: String,
    onTextChange: (String) -> Unit,
    icon: ToDoIcon,
    onIconChange: (ToDoIcon) -> Unit,
    iconsVisible: Boolean,
    submit: () -> Unit,
    buttonSlot: @Composable () -> Unit
) 


    Column 
        Row(
            Modifier
                .padding(horizontal = 16.dp)
                .padding(top = 16.dp)
        ) 

            TodoInputText(
                text = text,
                modifier = Modifier
                    .weight(1f)
                    .padding(end = 8.dp),
                onTextChanged = onTextChange,
                onImeAction = submit
            )
            Spacer(Modifier.width(8.dp))
            Box(modifier = Modifier.align(Alignment.CenterVertically)) 
                buttonSlot()
            

        
        AnimatedIconRow(
            visible = iconsVisible,
            icon = icon,
            onIconChange = onIconChange,
            modifier = Modifier.padding(8.dp)
        )

    


//一排图标 根据文本框是否有内容 自动弹起收缩
@Composable
fun AnimatedIconRow(
    modifier: Modifier = Modifier,
    visible: Boolean,
    icon: ToDoIcon,
    onIconChange: (ToDoIcon) -> Unit
) 
    val enter = remember 
        fadeIn(animationSpec = TweenSpec(300, easing = FastOutLinearInEasing))
    

    val exit = remember 
        fadeOut(animationSpec = TweenSpec(100, easing = FastOutSlowInEasing))
    
    //最小高度16dp
    AnimatedVisibility(visible = visible, enter = enter, exit = exit, modifier = modifier) 
        IconRow(icon = icon, onIconChange = onIconChange)
    




@Composable
fun IconRow(icon: ToDoIcon, onIconChange: (ToDoIcon) -> Unit, modifier: Modifier = Modifier) 
    Row(modifier = modifier) 
        for (todoIcon in ToDoIcon.values()) 
            SelectableIconButton(
                icon = todoIcon.imageVector,
                iconContentDescription = todoIcon.contentDescription,
                onIconSelect =  onIconChange(todoIcon) ,
                isSelected = todoIcon == icon
            )
        
    



@Composable
fun SelectableIconButton(
    icon: ImageVector,
    iconContentDescription: Int,
    onIconSelect: () -> Unit,
    isSelected: Boolean,
    modifier: Modifier = Modifier
) 
    //图标选中和未选中 颜色不一样
    val tint = if (isSelected) 
        MaterialTheme.colors.primary
     else 
        MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
    


    val enter = remember 
        fadeIn(animationSpec = TweenSpec(300, easing = FastOutLinearInEasing))
    

    val exit = remember 
        fadeOut(animationSpec = TweenSpec(100, easing = FastOutSlowInEasing))
    


    TextButton(onClick = onIconSelect, modifier = modifier) 
        Column 
            Icon(
                imageVector = icon,
                tint = tint,
                contentDescription = stringResource(id = iconContentDescription)
            )
            //最小高度16dp
            AnimatedVisibility(visible = isSelected, enter = enter, exit = exit) 
                Box(
                    Modifier
                        .padding(top = 3.dp)
                        .width(icon.defaultWidth)
                        .height(1.dp)
                        .background(tint)
                )
            
        

    

TodoScreen.kt
package com.anguomob.jecpack.activity.compose.todo.one

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anguomob.jecpack.activity.compose.todo.bean.TodoItem
import generateRandomTodoItem
import kotlin.random.Random


@Composable
fun TodoScreen(
    items: List<TodoItem>,
    currentlyEditing: TodoItem?,
    onAddItem: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    onStartEdit: (TodoItem) -> Unit,
    onEditItemChange: (TodoItem) -> Unit,
    onEditDone: () -> Unit,
) 
    Column() 
        val enableTopSection = currentlyEditing == null
        //输入框 外加一个灰色背景
        ToDoItemInputBackground(content = 
            if (enableTopSection) 
                TodoItemEntryInput(onItemComplete = onAddItem)
             else 
                Text(
                    text = "编辑条目",
                    style = MaterialTheme.typography.h6,
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .align(Alignment.CenterVertically)
                        .padding(16.dp)
                        .fillMaxWidth()
                )
            
        , elevatioon = true)
        LazyColumn(
            modifier = Modifier
                .weight(1f)
        ) 
            items(items)  todo ->
                if (currentlyEditing?.id == todo.id) 
                    TodoItemInLineEditor(
                        item = currentlyEditing,
                        onEditItemChange = onEditItemChange,
                        onEditDone = onEditDone,
                        onRemoveItem =  onRemove(todo) 
                    )
                
                TodoRow(todo, modifier = Modifier.fillParentMaxWidth(),onItemClick=onStartEdit)
            
        
        Button(
            onClick = 
                onAddItem(generateRandomTodoItem())

            ,
            modifier = Modifier
                .padding(16.dp)
                .fillMaxWidth()
        ) 
            Text("新建TODO")
        

    



@Composable
fun TodoRow(todo: TodoItem, modifier: Modifier = Modifier, onItemClick: (TodoItem) -> Unit) 
    Row(
        modifier = modifier
            .padding(horizontal = 16.dp, vertical = 8.dp)
            .clickable  onItemClick(todo) ,
        //子元素水平均匀分发
        horizontalArrangement = Arrangement.SpaceBetween
    ) 
        Text(todo.task)
        val iconAlpha = remember(todo.id) 
            randomTint()
        
        Icon(
            imageVector = todo.icon.imageVector,
            tint = LocalContentColor.current.copy(alpha = iconAlpha),
            contentDescription = stringResource(id = todo.icon.contentDescription)
        )
    



private fun randomTint(): Float 
    val number = Random.nextFloat().coerceIn(0.3f, 0.9f)
    return number

数据bean

TodoItem.kt
package com.anguomob.jecpack.activity.compose.todo.bean

import android.support.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.ui.graphics.vector.ImageVector
import com.anguomob.jecpack.R
import java.util.*

data class TodoItem(
    val task: String,
    val icon: ToDoIcon = ToDoIcon.Default,
    val id: UUID = UUID.randomUUID()
) 


enum class ToDoIcon(val imageVector: ImageVector, @StringRes val contentDescription: Int) 
    Square(Icons.Default.CropSquare, R.string.expand),
    Done(Icons.Default.Done, R.string.done),
    Event(Icons.Default.Event, R.string.event),
    PrivacyTip(Icons.Default.PrivacyTip, R.string.privacy),
    Trash(Icons.Default.RestoreFromTrash, R.string.restore),
    Default(Square.imageVector, Square.contentDescription)


viewMoel

package com.anguomob.jecpack.activity.compose.todo.viewmodel

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.anguomob.jecpack.activity.compose.todo.bean.TodoItem

class TodoViewModel : ViewModel() 
    //TODO 集合 只读
    var todoItems = mutableStateListOf<TodoItem>()
        private set


    //当前正在编辑的TodoItem的索引位置
    private var currentEditPosition by mutableStateOf(-1)
    val currentEditItem: TodoItem?
        get() = todoItems.getOrNull(currentEditPosition)

    fun addItem(item: TodoItem) 
        todoItems.add(item)
    

    fun removeItem(item: TodoItem) 
        todoItems.remove(item)
        onEditDone()

    

    fun onEditDone() 
        currentEditPosition = -1
    

    //当传入TodoItem列表中的条目被选中的时候,传入该对象,获取它在列表中的索引位置

    fun onEditItemSelected(item: TodoItem) 
        currentEditPosition = todoItems.indexOf(item)
    

    //todoItem编辑完成,重新给集合中的tood Item赋值 id属性不能修改,进行校验
    fun onEditItemChange(item: TodoItem) 
        todoItems.getOrNull(currentEditPosition)?.let 
            todoItems[currentEditPosition] = item
        


    

点击条目变成上方出现一个条目可以对其进行更新 删除 保存

 之前的新加逻辑依然存在

生成随机的测试数据

import com.anguomob.jecpack.activity.compose.todo.bean.ToDoIcon
import com.anguomob.jecpack.activity.compose.todo.bean.TodoItem

//
// Created by Administrator on 2022/6/20.
//

fun generateRandomTodoItem(): TodoItem 
    val message = listOf(
        "Learn compose",
        "Learn state",
        "Build dynamic UIs",
        "Learn Unidirectional Data Flow",
        "Integrate LiveData",
        "Integrate ViewModel",
        "Remember to saveState!",
        "Build stateless composables",
        "Use state from stateless composables",
    ).random()

    val icon = ToDoIcon.values().random()

    return TodoItem(message, icon)

以上是关于Kotlin Compose 终结toDo项目 点击可以编辑修改todo的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin Compose Todo小项目 删除添加项目 ,认识状态提升

Kotlin Jetpack Compose remember 给Icon添加透明度

Kotlin jetpack compose Tab的渲染 AnimatedVisibility的使用

Kotlin jetpack compose 文本输入框ExitText/TextField remember 居然可以传两个参数

android课程表控件悬浮窗Todo应用MVP框架Kotlin完整项目源码

Kotlin 1.5.0 上的 Jetpack Compose