公司动态
AztecEditor-Android开发实战:自定义工具栏与Task List功能实现教程
AztecEditor-Android开发实战自定义工具栏与Task List功能实现教程【免费下载链接】AztecEditor-AndroidA reusable native Android rich text editor component.项目地址: https://gitcode.com/gh_mirrors/az/AztecEditor-AndroidAztecEditor-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:idid/visualEditor android:layout_widthmatch_parent android:layout_heightmatch_parent android:scrollbarsvertical / org.wordpress.aztec.toolbar.AztecToolbar android:idid/toolbar android:layout_widthmatch_parent android:layout_heightwrap_content /在Activity中初始化编辑器val visualEditor findViewByIdAztecText(R.id.visualEditor) val toolbar findViewByIdAztecToolbar(R.id.toolbar) Aztec.with(visualEditor, toolbar, this) .setImageLoader(GlideImageLoader(this)) .setVideoThumbnailLoader(GlideVideoThumbnailLoader(this)) .build()️ 自定义工具栏配置详解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. 添加自定义按钮点击处理 findViewByIdButton(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功能由以下几个核心组件构成AztecTaskListSpan- 任务列表的Span实现TaskListClickHandler- 处理点击事件ListFormatter- 格式化逻辑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 Spanclass 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创建自定义Formatterclass 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-typetaskList属性!-- Task List的HTML表示 -- ul>val html visualEditor.toHtml() // 输出 // ul>// 添加自定义属性 val taskListSpan AztecTaskListSpan( nestingLevel 0, attributes AztecAttributes().apply { put(data-custom, value) put(data-priority, high) }, context context, listStyle listStyle ) // 生成的HTML会包含自定义属性 // ul /> 调试与问题排查在使用AztecEditor时可能会遇到一些常见问题这里提供解决方案。常见问题1Task List点击无响应// 检查是否正确设置了点击处理器 if (visualEditor.taskListClickHandler null) { visualEditor.setTaskListClickHandler(TaskListClickHandler(listStyle)) } // 检查边距计算 val listStyle BlockFormatter.ListStyle( leadingMargin 50, // 确保这个值足够大 indicatorMargin 10, indicatorColor Color.BLACK, verticalPadding 8, horizontalPadding 4 )常见问题2Task 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 mutableMapOfInt, 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: ListInt) { 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: MapString, 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>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(): ListTaskListItem { 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: ListTaskItem ) 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.ktSpan定义aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt格式化器aztec/src/main/kotlin/org/wordpress/aztec/formatting/ListFormatter.kt最佳实践性能优化在处理大量Task List时使用beginBatchEdit()和endBatchEdit()包装批量操作内存管理及时清理不再使用的Span对象避免内存泄漏用户体验为Task List点击提供视觉反馈如涟漪效果可访问性为复选框添加内容描述支持屏幕阅读器错误处理处理边界情况如空列表、越界点击等调试技巧// 启用调试日志 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-AndroidA reusable native Android rich text editor component.项目地址: https://gitcode.com/gh_mirrors/az/AztecEditor-Android创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考