二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销

百度去搜的话,极有可能会搜到和栈相关的东西,但是其实我们可以想的简单点,撤销的地方就是存一下上一步的文本内容,反撤销就是存着撤销中的内容,所以我们其实用两个类似列表的东西就能实现这个功能。

因此,以下的功能都是基于ArrayDeque来实现的,翻译一下Deque,它的中文名叫双队列。

不是很明白它的使用形式,但是应该是和列表差不多的。

思路很简单

撤销:填充上一步内容

反撤销:填充被撤销的内容

所以基于以上思路我们先创建变量:

class TextControl {

    var undoHistory = ArrayDeque()
    var redoHistory = ArrayDeque()
}

 1、撤销实现

fun undo(
        contentText: MutableState,
        unDoState: MutableState,
        reDoState: MutableState,
    ) {

        if (undoHistory.size > 1) {
            // pop the last
            reDoState.value = true
            redoHistory.add(undoHistory.removeLast())

            // peek the last
            contentText.value = undoHistory.last()
            remValTextBefore =
                TextFieldValue(contentText.value.text, TextRange(contentText.value.text.length))

            // 到这时,已经完成弹出,这时判断一下就能实现自己禁用自己
            if (undoHistory.size == 1) {
                unDoState.value = false
            }
        }
    }

既然是对内容操作,肯定会用到全局的内容变量contentText,而后面两个状态参数呢,是用来控制按钮的使能的,就是在撤销列表或者反撤销列表为空时让它不能点击(disable)

先看判断,判断它的size大于1,因为我们在初始化的时候要将内容放到撤销列表中,不然我们第一次操作将无法撤销。

当然,这个函数执行时,说明我们点击了撤销按钮,因此就要将列表的最后一个元素添加到反撤销列表中,并将撤销列表的最后一个元素赋值给内容变量。

这里还给remValTextBefore 进行了赋值,是我在后来调试的时候发现有点小bug,主要表现在如果不加这句的话,点击撤销一次后,反撤销会亮起,但是如果此时切换了光标,那么反撤销就会变暗,显然变化光标并没有对文本进行改动,所以不应该清空反撤销列表。

2、反撤销实现

fun redo(
        contentText: MutableState,
        unDoState: MutableState,
        reDoState: MutableState
    ) {
        if (redoHistory.size > 0) {
            val pop = redoHistory.removeLast()
            undoHistory.add(pop)
            unDoState.value = true
            contentText.value = pop
            remValTextBefore =
                TextFieldValue(contentText.value.text, TextRange(contentText.value.text.length))
            if (redoHistory.size == 0) {
                reDoState.value = false
            }
        }
    }

和撤销有区别的是,反撤销列表一开始不用存放任何内容,所以它的size一开始应该是0。

当我们点击反撤销时,就得将它的最后一个元素拿到,一边存入撤销列表,一边给内容变量。(注意和撤销函数的区别)

状态变量我就不说了,就是相互使能。

3、输入监测

在有了撤销和反撤销函数后,还要在文本变化过程中的正确时刻添加undoHistory,所以这里就会和上一篇讲的onInput函数有交集

首先是onInput函数的修改:

fun onInput(
        value: TextFieldValue,
        contentText: MutableState,
        unDoState: MutableState,
        reDoState: MutableState,
    )

接着要在初始化时保存文本内容到撤销列表:

if (remValTextBefore == null) {
            remValTextBefore = value
            undoHistory.add(value)
        } else {
            if (remValTextAfter == null) {
                remValTextAfter = value
            }
        }

之后就是在我们输入换行检测段代码的基础上添加了:

二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销_第1张图片

1、防止列表太大,所以设定了15的长度,就是最多连续点击撤销14次,因为它原本里面还放了一次。

2、文本发生变化时就要保存到撤销列表中

3、对两个按钮的状态进行实时判断,防止它在不该使能的时候使能。

这样这个功能就实现了,接下来要添加到控件中。

4、功能绑定

首先是声明

val textControl = remember { TextControl() }
    val unDoState = remember { mutableStateOf(false) }
    val reDoState = remember { mutableStateOf(false) }

 还是初始化和输入框处

二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销_第2张图片

二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销_第3张图片

接着是按钮

IconButton(
            onClick = {
                textControl.undo(contentText, unDoState, reDoState)
            },
            //通知设置layoutId实现控制约束
            modifier = Modifier
                .height(50.dp)
                .width(40.dp)
                .layoutId("btn_undo"),
            enabled = unDoState.value,

            ) {
            Icon(
                painter = painterResource(id = R.drawable.btn_undo),
                contentDescription = "add a book",
            )
        }
        IconButton(
            onClick = {
                textControl.redo(contentText, unDoState, reDoState)
            },
            //通知设置layoutId实现控制约束
            modifier = Modifier
                .height(50.dp)
                .width(40.dp)
                .layoutId("btn_redo"),
            enabled = reDoState.value
        ) {
            Icon(
                painter = painterResource(id = R.drawable.btn_redo),
                contentDescription = "add a book"
            )
        }

注意它们的enabled要设置成我们声明的状态。

这下基本就完成了!

还要在切换章节时清空两个列表,比如drawer中:

二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销_第4张图片

 剩下的底部工具栏的切换按钮也需要添加,这里就不展示了。

这下就完事了!看看效果:

二十、使用Jetpack Compsoe编写一个写小说的Android应用:【TextField应用】撤销与反撤销_第5张图片

你可能感兴趣的:(Android,android,kotlin,android,jetpack)