|  |  | 
 |  |  |     defStyleAttr: Int = 0 | 
 |  |  | ) : AppCompatTextView(context, attrs, defStyleAttr) { | 
 |  |  |  | 
 |  |  |     // 修改属性 | 
 |  |  |     private data class LegendItem( | 
 |  |  |         val selectedIcon: Drawable, | 
 |  |  |         val unselectedIcon: Drawable, | 
 |  |  |         val description: String, | 
 |  |  |         var isSelected: Boolean = true | 
 |  |  |     ) | 
 |  |  |      | 
 |  |  |     private var legendItems: List<LegendItem> = listOf() | 
 |  |  |     private var itemSpacing: Float = context.resources.displayMetrics.density * 16 // 图例项之间的水平间距 | 
 |  |  |     private var iconSize: Int = (24 * context.resources.displayMetrics.density).toInt() // 图标大小 | 
 |  |  |     private var iconTextSpacing: Float = context.resources.displayMetrics.density * 4 // 图标和文字之间的垂直间距 | 
 |  |  |  | 
 |  |  |     // 展开时显示的完整文字 | 
 |  |  |     private var expandedText: String = "" | 
 |  |  |     private var expandedText: String = "标注点: 当前位置\n区域: 配送范围" | 
 |  |  |     // 收起时显示的单个字符 | 
 |  |  |     private var collapsedText: String = "" | 
 |  |  |     private var collapsedText: String = "图例" | 
 |  |  |     // 当前是否处于展开状态 | 
 |  |  |     private var isExpanded: Boolean = false | 
 |  |  |     // 动画持续时间,默认300毫秒 | 
 |  |  | 
 |  |  |     // 三角形图标与文字的间距,默认为8dp | 
 |  |  |     private var triangleMargin: Float = 3 * context.resources.displayMetrics.density | 
 |  |  |  | 
 |  |  |     // 添加新属性 | 
 |  |  |     private var textLines: List<String> = listOf() | 
 |  |  |  | 
 |  |  |     // 添加点击回调接口 | 
 |  |  |     interface OnLegendItemClickListener { | 
 |  |  |         fun onLegendItemClick(position: Int, isSelected: Boolean) | 
 |  |  |     } | 
 |  |  |      | 
 |  |  |     private var legendItemClickListener: OnLegendItemClickListener? = null | 
 |  |  |  | 
 |  |  |     // 修改图例项的总高度计算 | 
 |  |  |     private val legendItemHeight: Int | 
 |  |  |         get() = iconSize + iconTextSpacing.toInt() + paint.textSize.toInt() + paint.descent().toInt() - paint.ascent().toInt() | 
 |  |  |  | 
 |  |  |     // 添加展开后的字体大小属性 | 
 |  |  |     private var expandedTextSize: Float = textSize | 
 |  |  |  | 
 |  |  |     // 添加一个变量保存默认字体大小 | 
 |  |  |     private var defaultTextSize: Float = 0f | 
 |  |  |  | 
 |  |  |     // 添加一个属性定义三角形图标的点击区域扩展范围 | 
 |  |  |     private val triangleClickPadding: Float = 15f * context.resources.displayMetrics.density // 20dp | 
 |  |  |  | 
 |  |  |     // 添加一个标识符,用于区分不同的 ExpandButton 实例 | 
 |  |  |     private var buttonId: String = "default" | 
 |  |  |  | 
 |  |  |     companion object { | 
 |  |  |         private const val PREFS_NAME = "expand_button_prefs" | 
 |  |  |         private const val KEY_LEGEND_STATES = "legend_states" | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     init { | 
 |  |  |         // 保存 XML 中设置的默认字体大小 | 
 |  |  |         defaultTextSize = textSize | 
 |  |  |  | 
 |  |  |         // 读取自定义属性 | 
 |  |  |         context.theme.obtainStyledAttributes( | 
 |  |  |             attrs, | 
 |  |  | 
 |  |  |         ).apply { | 
 |  |  |             try { | 
 |  |  |                 customLetterSpacing = getDimension(R.styleable.ExpandButton_letterSpacing, customLetterSpacing) | 
 |  |  |                 expandedText = getString(R.styleable.ExpandButton_expandedText) ?: "" | 
 |  |  |                 collapsedText = getString(R.styleable.ExpandButton_collapsedText) ?: "" | 
 |  |  |                 expandedText = getString(R.styleable.ExpandButton_expandedText) ?: "标注点: 当前位置\n区域: 配送范围" | 
 |  |  |                 collapsedText = getString(R.styleable.ExpandButton_collapsedText) ?: "图例" | 
 |  |  |                 animationDuration = getInteger(R.styleable.ExpandButton_animDuration, 300).toLong() | 
 |  |  |                 triangleMargin = getDimension(R.styleable.ExpandButton_triangleMargin, triangleMargin) | 
 |  |  |                 itemSpacing = getDimension(R.styleable.ExpandButton_itemSpacing, itemSpacing) | 
 |  |  |                 iconSize = getDimension(R.styleable.ExpandButton_iconSize, iconSize.toFloat()).toInt() | 
 |  |  |                 iconTextSpacing = getDimension(R.styleable.ExpandButton_iconTextSpacing, iconTextSpacing) | 
 |  |  |                 expandedTextSize = getDimension(R.styleable.ExpandButton_expandedTextSize, defaultTextSize) | 
 |  |  |             } finally { | 
 |  |  |                 recycle() | 
 |  |  |             } | 
 |  |  | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 设置文本可点击,仅在收起状态时响应点击展开 | 
 |  |  |         setOnClickListener { | 
 |  |  |             if (!isExpanded) { | 
 |  |  |                 toggleExpand() | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 添加触摸事件处理 | 
 |  |  |         setOnTouchListener { _, event -> | 
 |  |  |             when (event.action) { | 
 |  |  |                 MotionEvent.ACTION_DOWN -> { | 
 |  |  |                     // 检查点击是否在三角形图标区域内 | 
 |  |  |                     if (isClickOnTriangle(event.x)) { | 
 |  |  |                         toggleExpand() | 
 |  |  |                         return@setOnTouchListener true | 
 |  |  |                     } | 
 |  |  |                 } | 
 |  |  |             } | 
 |  |  |             false | 
 |  |  |         } | 
 |  |  |         // 修改触摸事件处理 | 
 |  |  |         setOnTouchListener(null) // 移除原有的触摸监听器 | 
 |  |  |          | 
 |  |  |         // 移除原有的点击监听器 | 
 |  |  |         setOnClickListener(null) | 
 |  |  |  | 
 |  |  |         // 设置左边距,为图标留出空间 | 
 |  |  |         compoundDrawablePadding = triangleMargin.toInt() | 
 |  |  | 
 |  |  |         ) | 
 |  |  |  | 
 |  |  |         // 设置单行显示,防止高度变化 | 
 |  |  |         maxLines = 1 | 
 |  |  |         isSingleLine = true | 
 |  |  |         maxLines = if (isExpanded) Int.MAX_VALUE else 1 | 
 |  |  |         isSingleLine = !isExpanded | 
 |  |  |          | 
 |  |  |         // 设置文字垂直居中 | 
 |  |  |         gravity = Gravity.CENTER_VERTICAL | 
 |  |  |  | 
 |  |  |         // 设置默认的内边距 | 
 |  |  |         val defaultPadding = (8 * context.resources.displayMetrics.density).toInt() | 
 |  |  |         setPadding( | 
 |  |  |             (16 * context.resources.displayMetrics.density + triangleMargin).toInt(), // 左边距增加,为图标留空间 | 
 |  |  |             defaultPadding, // 上边距 | 
 |  |  |             defaultPadding, // 右边距 | 
 |  |  |             defaultPadding  // 下边距 | 
 |  |  |         ) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | 
 |  |  |         super.onMeasure(widthMeasureSpec, heightMeasureSpec) | 
 |  |  |          | 
 |  |  |         if (isExpanded) { | 
 |  |  |             // 展开状态下的高度计算 | 
 |  |  |             val desiredHeight = legendItemHeight + paddingTop + paddingBottom | 
 |  |  |             setMeasuredDimension(measuredWidth, desiredHeight) | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     override fun onDraw(canvas: Canvas) { | 
 |  |  |         // 保存画布状态 | 
 |  |  |         // 绘制展开/收起图标 | 
 |  |  |         drawTriangle(canvas) | 
 |  |  |          | 
 |  |  |         if (!isExpanded) { | 
 |  |  |             // 收起状态使用默认字体大小 | 
 |  |  |             paint.textSize = defaultTextSize | 
 |  |  |             super.onDraw(canvas) | 
 |  |  |             return | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 展开状态使用展开后的字体大小 | 
 |  |  |         paint.textSize = expandedTextSize | 
 |  |  |          | 
 |  |  |         // 计算所有图例项中最宽的宽度 | 
 |  |  |         val maxWidth = legendItems.maxOf { item -> | 
 |  |  |             maxOf(paint.measureText(item.description), iconSize.toFloat()) | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 计算总宽度(添加首尾的边距) | 
 |  |  |         val totalWidth = legendItems.size * maxWidth +  | 
 |  |  |             (legendItems.size + 1) * itemSpacing  // 修改这里,添加一个额外的间距 | 
 |  |  |          | 
 |  |  |         // 计算起始x坐标,使整体水平居中 | 
 |  |  |         var x = (width - totalWidth) / 2 + itemSpacing  // 添加起始边距 | 
 |  |  |          | 
 |  |  |         // 计算垂直居中的y坐标,考虑上下边距 | 
 |  |  |         val centerY = height / 2f | 
 |  |  |          | 
 |  |  |         legendItems.forEachIndexed { index, item -> | 
 |  |  |             // 计算当前图例项的起始位置(移除index > 0的判断) | 
 |  |  |             val itemStartX = x | 
 |  |  |              | 
 |  |  |             // 计算图标的水平位置(居中于图例项) | 
 |  |  |             val iconLeft = itemStartX + (maxWidth - iconSize) / 2 | 
 |  |  |              | 
 |  |  |             // 计算文字的位置 | 
 |  |  |             val textWidth = paint.measureText(item.description) | 
 |  |  |             val textX = itemStartX + (maxWidth - textWidth) / 2 | 
 |  |  |              | 
 |  |  |             // 绘制图标,根据选中状态选择不同的图标 | 
 |  |  |             val iconTop = paddingTop + (height - legendItemHeight) / 2 | 
 |  |  |             val currentIcon = if (item.isSelected) item.selectedIcon else item.unselectedIcon | 
 |  |  |             currentIcon.setBounds( | 
 |  |  |                 iconLeft.toInt(), | 
 |  |  |                 iconTop.toInt(), | 
 |  |  |                 (iconLeft + iconSize).toInt(), | 
 |  |  |                 (iconTop + iconSize).toInt() | 
 |  |  |             ) | 
 |  |  |             currentIcon.draw(canvas) | 
 |  |  |              | 
 |  |  |             // 绘制文字,根据选中状态使用不同的颜色 | 
 |  |  |             paint.color = if (item.isSelected)  | 
 |  |  |                 currentTextColor  | 
 |  |  |             else  | 
 |  |  |                 0xFF999999.toInt() // 灰色 | 
 |  |  |             val textY = iconTop + iconSize + iconTextSpacing - paint.ascent() | 
 |  |  |             canvas.drawText(item.description, textX, textY, paint) | 
 |  |  |              | 
 |  |  |             // 更新下一个图例项的起始位置 | 
 |  |  |             x = itemStartX + maxWidth + itemSpacing | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 恢复画笔颜色 | 
 |  |  |         paint.color = currentTextColor | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     // 将原来的onDraw中的三角形绘制逻辑提取出来 | 
 |  |  |     private fun drawTriangle(canvas: Canvas) { | 
 |  |  |         canvas.save() | 
 |  |  |          | 
 |  |  |         // 计算图标位置 | 
 |  |  |         val iconSize = triangleDrawable.intrinsicWidth | 
 |  |  |         val iconLeft = paddingLeft - iconSize - compoundDrawablePadding | 
 |  |  |         val iconTop = (height - iconSize) / 2 | 
 |  |  |          | 
 |  |  |         // 设置图标边界 | 
 |  |  |         triangleDrawable.setBounds( | 
 |  |  |             iconLeft, | 
 |  |  |             iconTop, | 
 |  |  | 
 |  |  |             iconTop + iconSize | 
 |  |  |         ) | 
 |  |  |          | 
 |  |  |         // 旋转画布 | 
 |  |  |         canvas.rotate( | 
 |  |  |             triangleRotation, | 
 |  |  |             (iconLeft + iconSize / 2).toFloat(), | 
 |  |  |             (iconTop + iconSize / 2).toFloat() | 
 |  |  |         ) | 
 |  |  |          | 
 |  |  |         // 绘制图标 | 
 |  |  |         triangleDrawable.draw(canvas) | 
 |  |  |          | 
 |  |  |         // 恢复画布状态 | 
 |  |  |         canvas.restore() | 
 |  |  |          | 
 |  |  |         super.onDraw(canvas) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 切换展开/收起状态 | 
 |  |  |      * 使用ValueAnimator实现宽度动画和图标旋转 | 
 |  |  |      */ | 
 |  |  |     private fun toggleExpand() { | 
 |  |  |         isExpanded = !isExpanded | 
 |  |  |  | 
 |  |  |         // 计算收起和展开状态的宽度 | 
 |  |  |         val collapsedWidth = paint.measureText(collapsedText).toInt() + paddingLeft + paddingRight | 
 |  |  |         val collapsedWidth = run { | 
 |  |  |             paint.textSize = defaultTextSize | 
 |  |  |             val width = paint.measureText(collapsedText).toInt() + paddingLeft + paddingRight | 
 |  |  |             paint.textSize = if (isExpanded) expandedTextSize else defaultTextSize | 
 |  |  |             width | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         val expandedWidth = calculateExpandedWidth() | 
 |  |  |  | 
 |  |  |         // 创建宽度动画 | 
 |  |  | 
 |  |  |             duration = animationDuration | 
 |  |  |             addUpdateListener { animator -> | 
 |  |  |                 triangleRotation = animator.animatedValue as Float | 
 |  |  |                 invalidate() // 重绘以更新图标旋转 | 
 |  |  |                 invalidate() | 
 |  |  |             } | 
 |  |  |             start() | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 更新文本 | 
 |  |  |         // 更新文本和字体大小 | 
 |  |  |         if (isExpanded) { | 
 |  |  |             setExpandedClickableText() | 
 |  |  |             paint.textSize = expandedTextSize | 
 |  |  |         } else { | 
 |  |  |             text = collapsedText | 
 |  |  |             paint.textSize = defaultTextSize | 
 |  |  |             setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, defaultTextSize) | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         invalidate() | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 计算展开后的总宽度 | 
 |  |  |      */ | 
 |  |  |     private fun calculateExpandedWidth(): Int { | 
 |  |  |         val spaceWidth = paint.measureText(" ") * (customLetterSpacing / 10) | 
 |  |  |         // 计算所有字符的总宽度 | 
 |  |  |         val textWidth = expandedText.fold(0f) { acc, char -> | 
 |  |  |             acc + paint.measureText(char.toString()) | 
 |  |  |         // 临时保存当前字体大小 | 
 |  |  |         val currentTextSize = paint.textSize | 
 |  |  |         // 设置为展开状态的字体大小 | 
 |  |  |         paint.textSize = expandedTextSize | 
 |  |  |          | 
 |  |  |         try { | 
 |  |  |             // 计算所有图例项中最宽的宽度 | 
 |  |  |             val maxWidth = legendItems.maxOf { item -> | 
 |  |  |                 maxOf(paint.measureText(item.description), iconSize.toFloat()) | 
 |  |  |             } | 
 |  |  |              | 
 |  |  |             // 计算总宽度 = 所有图例项的宽度 + 所有间距(包括首尾) + 左右内边距 | 
 |  |  |             return (legendItems.size * maxWidth +  | 
 |  |  |                 (legendItems.size + 1) * itemSpacing +   | 
 |  |  |                 paddingLeft + paddingRight).toInt() | 
 |  |  |         } finally { | 
 |  |  |             // 恢复原来的字体大小 | 
 |  |  |             paint.textSize = currentTextSize | 
 |  |  |         } | 
 |  |  |         // 计算间距的总宽度(字符数量减1个间距) | 
 |  |  |         val spacesWidth = spaceWidth * (expandedText.length - 1) | 
 |  |  |         return (textWidth + spacesWidth).toInt() + paddingLeft + paddingRight | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  | 
 |  |  |      */ | 
 |  |  |     private fun setExpandedClickableText() { | 
 |  |  |         val builder = SpannableStringBuilder() | 
 |  |  |         expandedText.forEachIndexed { index, char -> | 
 |  |  |             // 添加字符 | 
 |  |  |             builder.append(char) | 
 |  |  |          | 
 |  |  |         // 计算所有图例项中最宽的宽度 | 
 |  |  |         val maxWidth = legendItems.maxOf { item -> | 
 |  |  |             maxOf(paint.measureText(item.description), iconSize.toFloat()) | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 计算总宽度 | 
 |  |  |         val totalWidth = legendItems.size * maxWidth +  | 
 |  |  |             (legendItems.size - 1) * itemSpacing | 
 |  |  |          | 
 |  |  |         // 计算整体水平居中需要的起始空格 | 
 |  |  |         val startPadding = ((width - totalWidth) / 2 / paint.measureText(" ")).toInt() | 
 |  |  |         if (startPadding > 0) { | 
 |  |  |             builder.append(" ".repeat(startPadding)) | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 添加垂直空间,为图标预留位置 | 
 |  |  |         val verticalSpaces = ((iconSize + iconTextSpacing) / paint.textSize).toInt() | 
 |  |  |         builder.append("\n".repeat(verticalSpaces)) | 
 |  |  |          | 
 |  |  |         legendItems.forEachIndexed { index, item -> | 
 |  |  |             if (index > 0) { | 
 |  |  |                 // 在图例项之间添加水平间距 | 
 |  |  |                 builder.append(" ".repeat((itemSpacing / paint.measureText(" ")).toInt())) | 
 |  |  |             } | 
 |  |  |              | 
 |  |  |             // 为字符设置点击事件 | 
 |  |  |             // 计算水平居中所需的空格数 | 
 |  |  |             val textWidth = paint.measureText(item.description) | 
 |  |  |             val paddingSpaces = ((maxWidth - textWidth) / 2 / paint.measureText(" ")).toInt() | 
 |  |  |              | 
 |  |  |             // 添加左侧空格实现居中 | 
 |  |  |             if (paddingSpaces > 0) { | 
 |  |  |                 builder.append(" ".repeat(paddingSpaces)) | 
 |  |  |             } | 
 |  |  |              | 
 |  |  |             // 添加描述文本 | 
 |  |  |             val startPosition = builder.length | 
 |  |  |             builder.append(item.description) | 
 |  |  |              | 
 |  |  |             // 添加右侧空格以确保宽度一致 | 
 |  |  |             val remainingSpaces = ((maxWidth - textWidth) / paint.measureText(" ")).toInt() - paddingSpaces | 
 |  |  |             if (remainingSpaces > 0) { | 
 |  |  |                 builder.append(" ".repeat(remainingSpaces)) | 
 |  |  |             } | 
 |  |  |              | 
 |  |  |             // 为文字设置点击事件 | 
 |  |  |             val clickableSpan = object : ClickableSpan() { | 
 |  |  |                 override fun onClick(view: View) { | 
 |  |  |                     onCharClickListener?.invoke(char, index) | 
 |  |  |                     onCharClickListener?.invoke(item.description[0], index) | 
 |  |  |                 } | 
 |  |  |  | 
 |  |  |                 override fun updateDrawState(ds: android.text.TextPaint) { | 
 |  |  |                     super.updateDrawState(ds) | 
 |  |  |                     // 移除下划线 | 
 |  |  |                     ds.isUnderlineText = false | 
 |  |  |                     // 保持原始文字颜色 | 
 |  |  |                     ds.color = currentTextColor | 
 |  |  |                 } | 
 |  |  |             } | 
 |  |  |              | 
 |  |  |             builder.setSpan( | 
 |  |  |                 clickableSpan, | 
 |  |  |                 builder.length - 1, | 
 |  |  |                 builder.length, | 
 |  |  |                 startPosition, | 
 |  |  |                 startPosition + item.description.length, | 
 |  |  |                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | 
 |  |  |             ) | 
 |  |  |              | 
 |  |  |             // 只在非最后一个字符后添加空格作为间距 | 
 |  |  |             if (index < expandedText.length - 1) { | 
 |  |  |                 builder.append(" ".repeat((customLetterSpacing / 10).toInt())) | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         // 设置文本对齐方式为居中 | 
 |  |  |         gravity = Gravity.CENTER | 
 |  |  |          | 
 |  |  |         text = builder | 
 |  |  |         // 启用LinkMovementMethod以响应ClickableSpan的点击事件 | 
 |  |  |         movementMethod = android.text.method.LinkMovementMethod.getInstance() | 
 |  |  |     } | 
 |  |  |  | 
 |  |  | 
 |  |  |     private fun isClickOnTriangle(x: Float): Boolean { | 
 |  |  |         val iconSize = triangleDrawable.intrinsicWidth | 
 |  |  |         val iconLeft = paddingLeft - iconSize - compoundDrawablePadding | 
 |  |  |         return x <= paddingLeft && x >= iconLeft | 
 |  |  |          | 
 |  |  |         // 扩大点击区域:左右各增加 triangleClickPadding | 
 |  |  |         return x <= (paddingLeft + triangleClickPadding) &&  | 
 |  |  |                x >= (iconLeft - triangleClickPadding) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  | 
 |  |  |             paddingBottom | 
 |  |  |         ) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 设置按钮的唯一标识符 | 
 |  |  |      * @param id 标识符 | 
 |  |  |      */ | 
 |  |  |     fun setButtonId(id: String) { | 
 |  |  |         this.buttonId = id | 
 |  |  |         // 加载保存的状态 | 
 |  |  |  | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 设置图例内容 | 
 |  |  |      */ | 
 |  |  |     @JvmName("setLegendsList") | 
 |  |  |     fun setLegends(items: List<Quadruple<Drawable, Drawable, String, Boolean>>) { | 
 |  |  |         legendItems = items.map { (selectedIcon, unselectedIcon, description, isSelected) -> | 
 |  |  |             selectedIcon.setBounds(0, 0, iconSize, iconSize) | 
 |  |  |             unselectedIcon.setBounds(0, 0, iconSize, iconSize) | 
 |  |  |             LegendItem(selectedIcon, unselectedIcon, description, isSelected) | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         if (!isExpanded) { | 
 |  |  |             text = collapsedText | 
 |  |  |         } else { | 
 |  |  |             invalidate() | 
 |  |  |         } | 
 |  |  |         requestLayout() | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     // 添加一个 Java 友好的方法 | 
 |  |  |     @JvmName("setLegendsArray") | 
 |  |  |     fun setLegends(vararg items: Quadruple<Drawable, Drawable, String, Boolean>) { | 
 |  |  |         setLegends(items.toList()) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     // 添加一个数据类来表示四元组 | 
 |  |  |     data class Quadruple<A, B, C, D>( | 
 |  |  |         val first: A, | 
 |  |  |         val second: B, | 
 |  |  |         val third: C, | 
 |  |  |         val fourth: D | 
 |  |  |     ) | 
 |  |  |  | 
 |  |  |     // 添加一个便捷的扩展函数来创建 Quadruple | 
 |  |  |     fun <A, B, C, D> quadrupleOf(first: A, second: B, third: C, fourth: D): Quadruple<A, B, C, D> { | 
 |  |  |         return Quadruple(first, second, third, fourth) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 添加设置监听器的方法 | 
 |  |  |      */ | 
 |  |  |     fun setOnLegendItemClickListener(listener: OnLegendItemClickListener) { | 
 |  |  |         legendItemClickListener = listener | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 处理触摸事件 | 
 |  |  |      */ | 
 |  |  |     override fun onTouchEvent(event: MotionEvent): Boolean { | 
 |  |  |         when (event.action) { | 
 |  |  |             MotionEvent.ACTION_DOWN -> { | 
 |  |  |                 // 检查点击是否在三角形图标区域内 | 
 |  |  |                 if (isClickOnTriangle(event.x)) { | 
 |  |  |                     toggleExpand() | 
 |  |  |                     return true | 
 |  |  |                 } | 
 |  |  |                  | 
 |  |  |                 // 如果是展开状态,检查是否点击了图例项 | 
 |  |  |                 if (isExpanded) { | 
 |  |  |                     val clickedIndex = getClickedItemIndex(event.x, event.y) | 
 |  |  |                     if (clickedIndex != -1) { | 
 |  |  |                         toggleItemSelection(clickedIndex) | 
 |  |  |                         return true | 
 |  |  |                     } | 
 |  |  |                 } else if (!isExpanded && event.x > paddingLeft) { | 
 |  |  |                     // 在收起状态下,点击非三角形区域也展开 | 
 |  |  |                     toggleExpand() | 
 |  |  |                     return true | 
 |  |  |                 } | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |         return super.onTouchEvent(event) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 获取点击位置对应的图例项索引 | 
 |  |  |      */ | 
 |  |  |     private fun getClickedItemIndex(x: Float, y: Float): Int { | 
 |  |  |         if (!isExpanded) return -1 | 
 |  |  |  | 
 |  |  |         val maxWidth = legendItems.maxOf { item -> | 
 |  |  |             maxOf(paint.measureText(item.description), iconSize.toFloat()) | 
 |  |  |         } | 
 |  |  |          | 
 |  |  |         val totalWidth = legendItems.size * maxWidth +  | 
 |  |  |             (legendItems.size - 1) * itemSpacing | 
 |  |  |          | 
 |  |  |         val startX = (width - totalWidth) / 2 | 
 |  |  |         val iconTop = paddingTop + (height - legendItemHeight) / 2 | 
 |  |  |         val iconBottom = iconTop + legendItemHeight | 
 |  |  |  | 
 |  |  |         // 检查垂直方向是否在图例项范围内 | 
 |  |  |         if (y < iconTop || y > iconBottom) return -1 | 
 |  |  |  | 
 |  |  |         // 检查水平方向点击的是哪个图例项 | 
 |  |  |         legendItems.forEachIndexed { index, _ -> | 
 |  |  |             val itemStartX = startX + index * (maxWidth + itemSpacing) | 
 |  |  |             val itemEndX = itemStartX + maxWidth | 
 |  |  |             if (x >= itemStartX && x <= itemEndX) { | 
 |  |  |                 return index | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |         return -1 | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 切换图例项的选中状态 | 
 |  |  |      */ | 
 |  |  |     private fun toggleItemSelection(index: Int) { | 
 |  |  |         if (index < 0 || index >= legendItems.size) return | 
 |  |  |          | 
 |  |  |         legendItems[index].isSelected = !legendItems[index].isSelected | 
 |  |  |         legendItemClickListener?.onLegendItemClick( | 
 |  |  |             index,  | 
 |  |  |             legendItems[index].isSelected | 
 |  |  |         ) | 
 |  |  |  | 
 |  |  |         invalidate() | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 设置展开后的字体大小 | 
 |  |  |      * @param size 字体大小(像素) | 
 |  |  |      */ | 
 |  |  |     fun setExpandedTextSize(size: Float) { | 
 |  |  |         this.expandedTextSize = size | 
 |  |  |         if (isExpanded) { | 
 |  |  |             invalidate() | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 设置展开后的字体大小(SP) | 
 |  |  |      * @param sp 字体大小(SP) | 
 |  |  |      */ | 
 |  |  |     fun setExpandedTextSizeSp(sp: Float) { | 
 |  |  |         setExpandedTextSize(sp * context.resources.displayMetrics.scaledDensity) | 
 |  |  |     } | 
 |  |  | } |