AztecEditor-Android开发实战:自定义工具栏与Task List功能实现教程

AztecEditor-Android开发实战:自定义工具栏与Task List功能实现教程

【免费下载链接】AztecEditor-Android A reusable native Android rich text editor component. 【免费下载链接】AztecEditor-Android 项目地址: https://gitcode.com/gh_mirrors/az/AztecEditor-Android

AztecEditor-Android是一个功能强大的原生Android富文本编辑器组件,专为HTML文档编辑而设计。这个开源项目基于EditText和Spannable API构建,提供了完整的富文本编辑体验。在本教程中,我将详细介绍如何自定义工具栏和实现Task List功能,帮助开发者快速上手这个优秀的Android富文本编辑器库。

🚀 快速开始:集成AztecEditor到你的项目

要使用AztecEditor,首先需要在你的build.gradle文件中添加依赖:

dependencies {
    implementation 'org.wordpress:aztec:latest-version'
}

然后在布局文件中添加编辑器组件:

<org.wordpress.aztec.AztecText
    android:id="@+id/visualEditor"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical" />

<org.wordpress.aztec.toolbar.AztecToolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

在Activity中初始化编辑器:

val visualEditor = findViewById<AztecText>(R.id.visualEditor)
val toolbar = findViewById<AztecToolbar>(R.id.toolbar)

Aztec.with(visualEditor, toolbar, this)
    .setImageLoader(GlideImageLoader(this))
    .setVideoThumbnailLoader(GlideVideoThumbnailLoader(this))
    .build()

AztecEditor界面

🛠️ 自定义工具栏配置详解

AztecEditor的工具栏系统非常灵活,支持完全自定义。工具栏的核心类是AztecToolbar,它提供了丰富的配置选项。

基本工具栏配置

工具栏按钮通过ToolbarAction枚举类定义,每个按钮都对应特定的文本格式:

// 启用特定格式按钮
toolbar.setToolbarItems(
    listOf(
        ToolbarAction.BOLD,
        ToolbarAction.ITALIC,
        ToolbarAction.UNDERLINE,
        ToolbarAction.LINK,
        ToolbarAction.UNORDERED_LIST,
        ToolbarAction.ORDERED_LIST,
        ToolbarAction.TASK_LIST  // Task List按钮
    )
)

// 设置工具栏点击监听器
toolbar.setToolbarListener(object : IAztecToolbarClickListener {
    override fun onToolbarFormatButtonClicked(format: ITextFormat, isChecked: Boolean) {
        // 处理格式按钮点击
        visualEditor.toggleFormatting(format)
    }
    
    override fun onToolbarMediaButtonClicked() {
        // 处理媒体按钮点击
    }
})

高级工具栏定制

你可以创建完全自定义的工具栏布局:

// 1. 创建自定义布局
val customToolbarLayout = R.layout.custom_toolbar

// 2. 绑定自定义按钮
toolbar.bindCustomLayout(customToolbarLayout)

// 3. 添加自定义按钮点击处理
findViewById<Button>(R.id.custom_button).setOnClickListener {
    // 执行自定义操作
    visualEditor.insertHtml("<custom-tag>内容</custom-tag>")
}

// 4. 动态显示/隐藏工具栏按钮
toolbar.showToolbarAction(ToolbarAction.TASK_LIST, true)  // 显示Task List按钮
toolbar.showToolbarAction(ToolbarAction.CODE, false)      // 隐藏代码按钮

工具栏状态管理

工具栏会自动根据光标位置更新按钮状态:

// 监听编辑器选择变化
visualEditor.setOnSelectionChangedListener { start, end ->
    val activeFormats = visualEditor.getActiveFormats()
    toolbar.updateToolbarState(activeFormats)
}

// 获取当前激活的格式
val activeFormats = visualEditor.getActiveFormats()
if (activeFormats.contains(AztecTextFormat.FORMAT_TASK_LIST)) {
    // 当前在Task List中
}

可视化编辑器界面

📋 Task List功能深度解析

Task List是AztecEditor的一个重要功能,它允许用户创建可勾选的待办事项列表。让我们深入了解其实现原理。

Task List的核心组件

Task List功能由以下几个核心组件构成:

  1. AztecTaskListSpan - 任务列表的Span实现
  2. TaskListClickHandler - 处理点击事件
  3. ListFormatter - 格式化逻辑
  4. ToolbarAction.TASK_LIST - 工具栏按钮

Task List Span实现

Task List的核心是AztecTaskListSpan类,它负责渲染复选框和跟踪选中状态:

// 查看Task List Span源码
// aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt

class AztecTaskListSpan(
    override var nestingLevel: Int,
    override var attributes: AztecAttributes = AztecAttributes(),
    context: Context,
    var listStyle: BlockFormatter.ListStyle = BlockFormatter.ListStyle(0, 0, 0, 0, 0),
    var onRefresh: ((AztecTaskListSpan) -> Unit)? = null
) : AztecListSpan(nestingLevel, listStyle.verticalPadding) {
    
    override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int,
                                   top: Int, baseline: Int, bottom: Int,
                                   text: CharSequence, start: Int, end: Int,
                                   first: Boolean, l: Layout) {
        // 绘制复选框
        val d: Drawable? = contextRef.get()?.resources?.getDrawable(R.drawable.ic_checkbox, null)
        // ... 绘制逻辑
    }
    
    fun canToggle(): Boolean {
        // 防止快速重复点击
        val canToggle = !toggled
        toggled = canToggle
        return canToggle
    }
}

点击事件处理

TaskListClickHandler负责处理用户点击复选框的操作:

// aztec/src/main/kotlin/org/wordpress/aztec/TaskListClickHandler.kt

class TaskListClickHandler(val listStyle: BlockFormatter.ListStyle) {
    fun handleTaskListClick(text: Spannable, off: Int, x: Int, startMargin: Int): Boolean {
        // 获取点击的Task List Span
        val clickedList = text.getSpans(off, off, AztecTaskListSpan::class.java)
            .maxByOrNull { it.nestingLevel } ?: return false
        
        // 计算点击位置
        val depthMultiplier = (clickedList.nestingLevel / 2) + 1
        val effectiveMargin = listStyle.leadingMargin() * depthMultiplier
        
        // 检查是否点击在复选框区域内
        if (x + startMargin > (effectiveMargin + AztecTaskListSpan.PADDING_SPACE)) return false
        
        // 切换选中状态
        val clickedLines = text.getSpans(off, off, AztecListItemSpan::class.java)
        val clickedLine = clickedLines.find {
            val spanStart = text.getSpanStart(it)
            spanStart == off || (spanStart == off - 1 && text.getSpanEnd(it) == off)
        }
        
        if (clickedLine != null && clickedList.canToggle()) {
            clickedLine.toggleCheck()
            clickedList.refresh()
            return true
        }
        return false
    }
}

启用Task List功能

要在编辑器中启用Task List功能,需要进行以下配置:

// 1. 在Aztec初始化时启用Task List
Aztec.with(visualEditor, toolbar, this)
    .setImageLoader(GlideImageLoader(this))
    .enableTaskList()  // 启用Task List功能
    .build()

// 2. 配置Task List样式
val listStyle = BlockFormatter.ListStyle(
    leadingMargin = 50,      // 左边距
    indicatorMargin = 10,    // 指示器边距
    indicatorColor = Color.BLACK,  // 指示器颜色
    verticalPadding = 8,     // 垂直内边距
    horizontalPadding = 4    // 水平内边距
)

// 3. 设置点击处理器
visualEditor.setTaskListClickHandler(TaskListClickHandler(listStyle))

// 4. 确保工具栏包含Task List按钮
toolbar.setToolbarItems(
    listOf(
        // ... 其他按钮
        ToolbarAction.TASK_LIST,
        ToolbarAction.UNORDERED_LIST,
        ToolbarAction.ORDERED_LIST
    )
)

🔧 实战:创建自定义Task List样式

让我们创建一个自定义样式的Task List,支持不同的复选框样式和颜色。

步骤1:创建自定义Task List Span

class CustomTaskListSpan(
    nestingLevel: Int,
    attributes: AztecAttributes = AztecAttributes(),
    context: Context,
    listStyle: BlockFormatter.ListStyle,
    val checkboxColor: Int = Color.BLUE,
    val checkedDrawableRes: Int = R.drawable.custom_checkbox_checked,
    val uncheckedDrawableRes: Int = R.drawable.custom_checkbox_unchecked
) : AztecTaskListSpan(nestingLevel, attributes, context, listStyle) {
    
    override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int,
                                   top: Int, baseline: Int, bottom: Int,
                                   text: CharSequence, start: Int, end: Int,
                                   first: Boolean, l: Layout) {
        if (!first || contextRef.get() == null) return
        
        val spanStart = (text as Spanned).getSpanStart(this)
        val spanEnd = text.getSpanEnd(this)
        
        if (start !in spanStart..spanEnd || end !in spanStart..spanEnd) return
        
        val lineIndex = getIndexOfProcessedLine(text, end) ?: return
        
        // 使用自定义Drawable
        val drawableRes = if (isChecked(text, lineIndex)) {
            checkedDrawableRes
        } else {
            uncheckedDrawableRes
        }
        
        val d = contextRef.get()?.resources?.getDrawable(drawableRes, null)
        d?.setTint(checkboxColor)
        
        // 计算绘制位置
        val drawableHeight = (0.8 * (p.fontMetrics.bottom - p.fontMetrics.top))
        val markerStartPosition: Float = x + (listStyle.indicatorMargin * dir) * 1f
        
        d?.setBounds(
            (markerStartPosition - drawableHeight * 0.8).toInt().coerceAtLeast(0),
            (baseline - drawableHeight * 0.8).toInt(),
            (markerStartPosition + drawableHeight * 0.2).toInt(),
            (baseline + drawableHeight * 0.2).toInt()
        )
        d?.draw(c)
    }
}

步骤2:创建自定义Formatter

class CustomTaskListFormatter(
    private val editor: AztecText,
    private val checkboxColor: Int = Color.BLUE
) : IFormatter {
    
    override fun applyFormat(start: Int, end: Int) {
        val text = editor.text as SpannableStringBuilder
        
        // 创建自定义Task List Span
        val taskListSpan = CustomTaskListSpan(
            nestingLevel = calculateNestingLevel(text, start),
            context = editor.context,
            listStyle = BlockFormatter.ListStyle(
                leadingMargin = 60,
                indicatorMargin = 15,
                indicatorColor = checkboxColor,
                verticalPadding = 10,
                horizontalPadding = 5
            ),
            checkboxColor = checkboxColor
        )
        
        // 应用Span
        text.setSpan(
            taskListSpan,
            start,
            end,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        
        // 添加列表项
        applyListItemFormat(text, start, end, taskListSpan.nestingLevel + 1)
    }
    
    private fun calculateNestingLevel(text: Spanned, position: Int): Int {
        // 计算嵌套层级逻辑
        return 0
    }
    
    private fun applyListItemFormat(text: SpannableStringBuilder, start: Int, end: Int, nestingLevel: Int) {
        // 应用列表项格式
    }
}

步骤3:集成到编辑器

// 注册自定义Formatter
val customFormatter = CustomTaskListFormatter(visualEditor, Color.RED)
visualEditor.registerFormatter(AztecTextFormat.FORMAT_TASK_LIST, customFormatter)

// 或者替换默认的Task List处理
visualEditor.setTaskListFormatter(customFormatter)

🎯 高级功能:Task List与HTML转换

AztecEditor支持Task List与HTML之间的双向转换,这是其核心功能之一。

HTML到Task List的解析

当从HTML解析时,Aztec会自动识别<ul>标签的data-type="taskList"属性:

<!-- Task List的HTML表示 -->
<ul data-type="taskList">
    <li data-checked="true">已完成的任务</li>
    <li data-checked="false">未完成的任务</li>
</ul>

Task List到HTML的生成

当将编辑器内容转换为HTML时:

val html = visualEditor.toHtml()
// 输出:
// <ul data-type="taskList">
//   <li data-checked="true">任务1</li>
//   <li data-checked="false">任务2</li>
// </ul>

自定义HTML属性

你可以扩展Task List的HTML属性:

// 添加自定义属性
val taskListSpan = AztecTaskListSpan(
    nestingLevel = 0,
    attributes = AztecAttributes().apply {
        put("data-custom", "value")
        put("data-priority", "high")
    },
    context = context,
    listStyle = listStyle
)

// 生成的HTML会包含自定义属性
// <ul data-type="taskList" data-custom="value" data-priority="high">...</ul>

代码编辑器界面

🔍 调试与问题排查

在使用AztecEditor时,可能会遇到一些常见问题,这里提供解决方案。

常见问题1:Task List点击无响应

// 检查是否正确设置了点击处理器
if (visualEditor.taskListClickHandler == null) {
    visualEditor.setTaskListClickHandler(TaskListClickHandler(listStyle))
}

// 检查边距计算
val listStyle = BlockFormatter.ListStyle(
    leadingMargin = 50,  // 确保这个值足够大
    indicatorMargin = 10,
    indicatorColor = Color.BLACK,
    verticalPadding = 8,
    horizontalPadding = 4
)

常见问题2:Task List样式不正确

// 1. 检查Drawable资源
val drawable = context.resources.getDrawable(R.drawable.ic_checkbox, null)
if (drawable == null) {
    // Drawable资源不存在
    Log.e("Aztec", "Checkbox drawable not found")
}

// 2. 检查颜色配置
val typedArray = context.obtainStyledAttributes(
    intArrayOf(android.R.attr.colorControlNormal)
)
val defaultColor = typedArray.getColor(0, Color.BLACK)
typedArray.recycle()

// 3. 调试绘制边界
override fun drawLeadingMargin(...) {
    Log.d("Aztec", "Drawing bounds: $left, $top, $right, $bottom")
    // ... 绘制逻辑
}

常见问题3:嵌套Task List问题

// 处理嵌套Task List
fun handleNestedTaskList(text: Spannable, position: Int) {
    val taskListSpans = text.getSpans(position, position, AztecTaskListSpan::class.java)
    
    // 获取最内层的Task List
    val innermostSpan = taskListSpans.maxByOrNull { it.nestingLevel }
    
    // 根据嵌套层级调整边距
    val nestingLevel = innermostSpan?.nestingLevel ?: 0
    val adjustedMargin = listStyle.leadingMargin() * (nestingLevel + 1)
    
    // 处理点击事件
    // ...
}

📊 性能优化建议

1. Span管理优化

// 避免频繁创建Span对象
val spanCache = mutableMapOf<Int, AztecTaskListSpan>()

fun getOrCreateTaskListSpan(nestingLevel: Int): AztecTaskListSpan {
    return spanCache.getOrPut(nestingLevel) {
        AztecTaskListSpan(
            nestingLevel = nestingLevel,
            context = context,
            listStyle = listStyle
        )
    }
}

2. 批量操作优化

// 批量应用Task List格式
fun applyTaskListToMultipleLines(editor: AztecText, lineIndices: List<Int>) {
    editor.beginBatchEdit()
    try {
        lineIndices.forEach { lineIndex ->
            val lineStart = editor.layout.getLineStart(lineIndex)
            val lineEnd = editor.layout.getLineEnd(lineIndex)
            editor.toggleFormatting(AztecTextFormat.FORMAT_TASK_LIST, lineStart, lineEnd)
        }
    } finally {
        editor.endBatchEdit()
    }
}

3. 内存优化

// 使用WeakReference避免内存泄漏
class SafeTaskListSpan(context: Context) {
    private val contextRef = WeakReference(context)
    
    fun draw(canvas: Canvas) {
        val context = contextRef.get()
        if (context != null) {
            // 安全地使用context
            val drawable = context.resources.getDrawable(R.drawable.ic_checkbox, null)
            // ... 绘制逻辑
        }
    }
}

🎨 扩展功能:自定义Task List主题

你可以创建不同主题的Task List来满足不同的设计需求。

主题配置类

data class TaskListTheme(
    val checkboxColor: Int,
    val checkedDrawable: Int,
    val uncheckedDrawable: Int,
    val textColor: Int,
    val backgroundColor: Int,
    val priorityColors: Map<String, Int> = mapOf(
        "high" to Color.RED,
        "medium" to Color.YELLOW,
        "low" to Color.GREEN
    )
)

// 预定义主题
object TaskListThemes {
    val DEFAULT = TaskListTheme(
        checkboxColor = Color.BLACK,
        checkedDrawable = R.drawable.checkbox_default_checked,
        uncheckedDrawable = R.drawable.checkbox_default_unchecked,
        textColor = Color.BLACK,
        backgroundColor = Color.TRANSPARENT
    )
    
    val DARK = TaskListTheme(
        checkboxColor = Color.WHITE,
        checkedDrawable = R.drawable.checkbox_dark_checked,
        uncheckedDrawable = R.drawable.checkbox_dark_unchecked,
        textColor = Color.WHITE,
        backgroundColor = Color.DKGRAY
    )
    
    val COLORFUL = TaskListTheme(
        checkboxColor = Color.BLUE,
        checkedDrawable = R.drawable.checkbox_colorful_checked,
        uncheckedDrawable = R.drawable.checkbox_colorful_unchecked,
        textColor = Color.BLUE,
        backgroundColor = Color.LTGRAY
    )
}

动态主题切换

class ThemedTaskListSpan(
    nestingLevel: Int,
    attributes: AztecAttributes = AztecAttributes(),
    context: Context,
    listStyle: BlockFormatter.ListStyle,
    var theme: TaskListTheme = TaskListThemes.DEFAULT
) : AztecTaskListSpan(nestingLevel, attributes, context, listStyle) {
    
    fun setTheme(newTheme: TaskListTheme) {
        theme = newTheme
        refresh()  // 触发重绘
    }
    
    override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int,
                                   top: Int, baseline: Int, bottom: Int,
                                   text: CharSequence, start: Int, end: Int,
                                   first: Boolean, l: Layout) {
        // 使用主题颜色
        p.color = theme.checkboxColor
        
        // 根据优先级使用不同颜色
        val priority = attributes.getValue("data-priority")
        val priorityColor = theme.priorityColors[priority] ?: theme.checkboxColor
        p.color = priorityColor
        
        // ... 绘制逻辑
    }
}

📱 实际应用场景

场景1:待办事项应用

class TodoListEditor : AppCompatActivity() {
    private lateinit var editor: AztecText
    private lateinit var toolbar: AztecToolbar
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_todo_editor)
        
        editor = findViewById(R.id.editor)
        toolbar = findViewById(R.id.toolbar)
        
        // 配置为待办事项专用编辑器
        configureTodoEditor()
        
        // 加载示例待办事项
        loadSampleTodos()
    }
    
    private fun configureTodoEditor() {
        Aztec.with(editor, toolbar, this)
            .enableTaskList()
            .setToolbarItems(listOf(
                ToolbarAction.TASK_LIST,
                ToolbarAction.BOLD,
                ToolbarAction.ITALIC,
                ToolbarAction.LINK
            ))
            .build()
        
        // 自定义Task List样式
        val todoStyle = BlockFormatter.ListStyle(
            leadingMargin = 60,
            indicatorMargin = 15,
            indicatorColor = Color.BLUE,
            verticalPadding = 12,
            horizontalPadding = 8
        )
        
        editor.setTaskListClickHandler(TaskListClickHandler(todoStyle))
    }
    
    private fun loadSampleTodos() {
        val sampleHtml = """
            <ul data-type="taskList">
                <li data-checked="false">购买 groceries</li>
                <li data-checked="true">完成工作报告</li>
                <li data-checked="false">预约医生</li>
            </ul>
        """.trimIndent()
        
        editor.fromHtml(sampleHtml)
    }
    
    fun saveTodos() {
        val todosHtml = editor.toHtml()
        // 保存到数据库或文件
        saveToDatabase(todosHtml)
    }
}

场景2:项目管理工具

class ProjectTaskEditor : Fragment() {
    // ... 初始化代码
    
    fun setupTaskPriorities() {
        // 为不同优先级的任务设置不同样式
        val highPriorityStyle = BlockFormatter.ListStyle(
            leadingMargin = 60,
            indicatorMargin = 15,
            indicatorColor = Color.RED,  // 高优先级用红色
            verticalPadding = 12,
            horizontalPadding = 8
        )
        
        val mediumPriorityStyle = BlockFormatter.ListStyle(
            leadingMargin = 60,
            indicatorMargin = 15,
            indicatorColor = Color.YELLOW,  // 中优先级用黄色
            verticalPadding = 12,
            horizontalPadding = 8
        )
        
        val lowPriorityStyle = BlockFormatter.ListStyle(
            leadingMargin = 60,
            indicatorMargin = 15,
            indicatorColor = Color.GREEN,  // 低优先级用绿色
            verticalPadding = 12,
            horizontalPadding = 8
        )
        
        // 根据任务优先级应用不同样式
        applyPriorityStyles()
    }
    
    fun exportToMarkdown(): String {
        val html = editor.toHtml()
        // 将HTML格式的Task List转换为Markdown
        return convertTaskListToMarkdown(html)
    }
}

🔧 工具函数与扩展

扩展函数

// 为AztecText添加扩展函数
fun AztecText.toggleTaskListAtLine(line: Int) {
    val layout = this.layout
    val lineStart = layout.getLineStart(line)
    val lineEnd = layout.getLineEnd(line)
    this.toggleFormatting(AztecTextFormat.FORMAT_TASK_LIST, lineStart, lineEnd)
}

fun AztecText.getTaskListItems(): List<TaskListItem> {
    val text = this.text as SpannableString
    val taskListSpans = text.getSpans(0, text.length, AztecTaskListSpan::class.java)
    
    return taskListSpans.map { taskListSpan ->
        val start = text.getSpanStart(taskListSpan)
        val end = text.getSpanEnd(taskListSpan)
        val items = text.getSpans(start, end, AztecListItemSpan::class.java)
        
        TaskListItem(
            span = taskListSpan,
            items = items.map { itemSpan ->
                val itemStart = text.getSpanStart(itemSpan)
                val itemEnd = text.getSpanEnd(itemSpan)
                val isChecked = itemSpan.attributes.getValue("checked") == "true"
                val text = text.substring(itemStart, itemEnd)
                
                TaskItem(text, isChecked)
            }
        )
    }
}

data class TaskListItem(
    val span: AztecTaskListSpan,
    val items: List<TaskItem>
)

data class TaskItem(
    val text: String,
    val isChecked: Boolean
)

工具类

object TaskListUtils {
    
    fun calculateCompletionPercentage(editor: AztecText): Float {
        val taskItems = editor.getTaskListItems()
        if (taskItems.isEmpty()) return 0f
        
        val totalItems = taskItems.sumOf { it.items.size }
        val completedItems = taskItems.sumOf { taskList ->
            taskList.items.count { it.isChecked }
        }
        
        return if (totalItems > 0) {
            completedItems.toFloat() / totalItems * 100
        } else {
            0f
        }
    }
    
    fun getTaskStatistics(editor: AztecText): TaskStatistics {
        val taskItems = editor.getTaskListItems()
        
        return TaskStatistics(
            totalTaskLists = taskItems.size,
            totalTasks = taskItems.sumOf { it.items.size },
            completedTasks = taskItems.sumOf { taskList ->
                taskList.items.count { it.isChecked }
            },
            completionPercentage = calculateCompletionPercentage(editor)
        )
    }
    
    data class TaskStatistics(
        val totalTaskLists: Int,
        val totalTasks: Int,
        val completedTasks: Int,
        val completionPercentage: Float
    )
}

📚 学习资源与最佳实践

官方文档路径

  • 核心编辑器实现:aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt
  • 工具栏系统:aztec/src/main/kotlin/org/wordpress/aztec/toolbar/
  • Task List实现:aztec/src/main/kotlin/org/wordpress/aztec/TaskListClickHandler.kt
  • Span定义:aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt
  • 格式化器:aztec/src/main/kotlin/org/wordpress/aztec/formatting/ListFormatter.kt

最佳实践

  1. 性能优化:在处理大量Task List时,使用beginBatchEdit()和endBatchEdit()包装批量操作
  2. 内存管理:及时清理不再使用的Span对象,避免内存泄漏
  3. 用户体验:为Task List点击提供视觉反馈,如涟漪效果
  4. 可访问性:为复选框添加内容描述,支持屏幕阅读器
  5. 错误处理:处理边界情况,如空列表、越界点击等

调试技巧

// 启用调试日志
fun enableDebugLogging() {
    // 添加Span变化监听
    editor.addSpanWatcher(object : SpanWatcher {
        override fun onSpanAdded(text: Spannable?, what: Any?, start: Int, end: Int) {
            if (what is AztecTaskListSpan) {
                Log.d("AztecDebug", "TaskListSpan added: $start-$end, nesting: ${what.nestingLevel}")
            }
        }
        
        override fun onSpanRemoved(text: Spannable?, what: Any?, start: Int, end: Int) {
            // 处理Span移除
        }
        
        override fun onSpanChanged(text: Spannable?, what: Any?, ostart: Int, oend: Int, nstart: Int, nend: Int) {
            // 处理Span变化
        }
    })
    
    // 跟踪点击事件
    editor.setOnTouchListener { v, event ->
        if (event.action == MotionEvent.ACTION_DOWN) {
            val x = event.x.toInt()
            val y = event.y.toInt()
            val offset = editor.getOffsetForPosition(x, y)
            Log.d("AztecDebug", "Touch at ($x, $y), offset: $offset")
        }
        false
    }
}

🎉 总结

通过本教程,你已经全面掌握了AztecEditor-Android中自定义工具栏和Task List功能的实现方法。AztecEditor提供了强大而灵活的API,让你能够创建功能丰富的富文本编辑器。

关键要点:

  • AztecEditor基于Span系统,性能优秀且扩展性强
  • 工具栏完全可定制,支持自定义按钮和布局
  • Task List功能完善,支持嵌套、点击事件和HTML转换
  • 良好的架构设计,便于二次开发和功能扩展

无论是构建简单的笔记应用还是复杂的内容管理系统,AztecEditor都能提供强大的编辑能力。通过合理利用其插件系统和扩展机制,你可以创建出功能丰富、用户体验优秀的Android富文本编辑器。

现在就开始使用AztecEditor-Android,为你的Android应用添加专业的富文本编辑功能吧!

【免费下载链接】AztecEditor-Android A reusable native Android rich text editor component. 【免费下载链接】AztecEditor-Android 项目地址: https://gitcode.com/gh_mirrors/az/AztecEditor-Android

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值