Android笔记(三十四):封装带省略号图标结尾的TextView

news2024/12/27 6:03:53

背景

项目需求需要实现在文本末尾显示一个icon,如果文本很长时则在省略号后面显示icon,使用TextView自带的drawableEnd可以实现,但是如果文本换行了则会显示在TextView垂直居中的位置,不满足要求,于是有了本篇的自定义View

效果

在这里插入图片描述

原理分析

在setText的时候计算icon插入的位置,这里采用文本预加载,才能让DynamicLayout计算出准确的行数

override fun setText(text: CharSequence, type: BufferType) {
        mOrigText = text
        mBufferType = type
        setTextInternal(fixTextInternal(), type)
        post {
            setTextInternal(fixTextInternal(), mBufferType)
            alpha = 1f
        }
    }

这里“+”用于图片占位符

val tmpSSb = SpannableStringBuilder(mOrigText)
        tmpSSb.append(getContentOfString(mGapToExpandHint))
        if (imgSpan1 != null) {
            tmpSSb.append("+")
            tmpSSb.setSpan(
                imgSpan1,
                tmpSSb.length - 1,
                tmpSSb.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }

这个算出最后一行除去占位icon的文本索引起始点和末尾点

val indexEnd = validLayout.getLineEnd(mMaxLinesOnShrink - 1)
val indexStart = validLayout.getLineStart(mMaxLinesOnShrink - 1)
var indexEndTrimmed = (indexEnd
	- getLengthOfString(mEllipsisHint)
	- getLengthOfString(mGapToExpandHint))
if (indexEndTrimmed <= indexStart) {
	indexEndTrimmed = indexEnd
}

indexEndTrimmed为去掉省略号图标后的文本末尾索引,以下需要进一步修正该索引,得出准确的值indexEndTrimmedRevised,将mOrigText进行文本裁剪再加上省略号图标后返回出去

        val remainWidth = validLayout.width - (mTextPaint!!.measureText(
            mOrigText!!.subSequence(indexStart, indexEndTrimmed).toString()
        ) + 0.5).toInt() - (bitmap1?.width ?: 0)
        val widthTailReplaced = mTextPaint!!.measureText(
            getContentOfString(mEllipsisHint)
                    + getContentOfString(mGapToExpandHint)
        )
        var indexEndTrimmedRevised = indexEndTrimmed
        if (remainWidth > widthTailReplaced) {
            var extraOffset = 0
            var extraWidth = 0
            while (remainWidth > widthTailReplaced + extraWidth) {
                extraOffset++
                extraWidth = if (indexEndTrimmed + extraOffset <= mOrigText!!.length) {
                    (mTextPaint!!.measureText(
                        mOrigText!!.subSequence(indexEndTrimmed, indexEndTrimmed + extraOffset)
                            .toString()
                    ) + 0.5).toInt()
                } else {
                    break
                }
            }
            indexEndTrimmedRevised += extraOffset - 1
        } else {
            var extraOffset = 0
            var extraWidth = 0
            while (remainWidth + extraWidth < widthTailReplaced) {
                extraOffset--
                extraWidth = if (indexEndTrimmed + extraOffset > indexStart) {
                    (mTextPaint!!.measureText(
                        mOrigText!!.subSequence(
                            indexEndTrimmed + extraOffset,
                            indexEndTrimmed
                        ).toString()
                    ) + 0.5).toInt()
                } else {
                    break
                }
            }
            indexEndTrimmedRevised += extraOffset
        }

完整源码

class EllipsisIconTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    companion object {
        private const val GAP_TO_EXPAND_HINT = " "
        private const val MAX_LINES_ON_SHRINK = 3
    }

    private var mEllipsisHint: String? = null
    private var mGapToExpandHint: String? = GAP_TO_EXPAND_HINT
    private var mMaxLinesOnShrink = MAX_LINES_ON_SHRINK
    private var mBufferType = BufferType.NORMAL
    private var mTextPaint: TextPaint? = null
    private var mLayout: Layout? = null
    private var mTextLineCount = -1
    private var mLayoutWidth = 0
    private var mFutureTextViewWidth = 0
    private var mEllipsisIcon: Int = 0
    private var mOrigText: CharSequence? = null
    private var bitmap1: Bitmap? = null
    private var imgSpan1: ImageSpan? = null
    private var isIconAlign = false


    init {
        var ellipsisIconWidth = 0
        var ellipsisIconHeight = 0
        if (attrs != null) {
            val a = context.obtainStyledAttributes(attrs, R.styleable.EllipsisIconTextView)
            val n = a.indexCount
            for (i in 0 until n) {
                when (val attr = a.getIndex(i)) {
                    R.styleable.EllipsisIconTextView_maxLinesOnShrink -> {
                        mMaxLinesOnShrink = a.getInteger(attr, MAX_LINES_ON_SHRINK)
                    }
                    R.styleable.EllipsisIconTextView_ellipsisHint -> {
                        mEllipsisHint = a.getString(attr)
                    }
                    R.styleable.EllipsisIconTextView_gapToExpandHint -> {
                        mGapToExpandHint = a.getString(attr)
                    }
                    R.styleable.EllipsisIconTextView_ellipsisIcon -> {
                        mEllipsisIcon = a.getResourceId(attr, 0)
                    }
                    R.styleable.EllipsisIconTextView_ellipsisIconAlign -> {
                        isIconAlign = a.getBoolean(attr, false)
                    }
                    R.styleable.EllipsisIconTextView_ellipsisIconWidth -> {
                        ellipsisIconWidth = a.getDimensionPixelSize(attr, 0)
                    }
                    R.styleable.EllipsisIconTextView_ellipsisIconHeight -> {
                        ellipsisIconHeight = a.getDimensionPixelSize(attr, 0)
                    }
                }
            }
            a.recycle()
        }

        bitmap1 = BitmapFactory.decodeResource(resources, mEllipsisIcon)
        val drawable = if (mEllipsisIcon == 0) null else AppCompatResources.getDrawable(context, mEllipsisIcon)
        drawable?.let {
            if (ellipsisIconWidth > 0 && ellipsisIconHeight > 0) {
                drawable.setBounds(0, 0, ellipsisIconWidth, ellipsisIconHeight)
            } else {
                drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
            }
            imgSpan1 = if (isIconAlign) CenteredImageSpan(drawable) else ImageSpan(drawable)
        }
        alpha = 0f
    }

    fun updateForRecyclerView(text: CharSequence, futureTextViewWidth: Int) {
        mFutureTextViewWidth = futureTextViewWidth
        setText(text, BufferType.NORMAL)
    }

    fun updateForRecyclerView(text: CharSequence, type: BufferType, futureTextViewWidth: Int) {
        mFutureTextViewWidth = futureTextViewWidth
        setText(text, type)
    }

    fun setMaxLinesOnShrink(text: CharSequence, mMaxLinesOnShrink: Int) {
        this.mMaxLinesOnShrink = mMaxLinesOnShrink
        setText(text, BufferType.NORMAL)
    }

    private fun fixTextInternal(): CharSequence? {
        if (TextUtils.isEmpty(mOrigText)) {
            return mOrigText
        }
        mLayout = layout
        if (mLayout != null) {
            mLayoutWidth = mLayout!!.width
        }
        if (mLayoutWidth <= 0) {
            mLayoutWidth = if (width == 0) {
                if (mFutureTextViewWidth == 0) {
                    return mOrigText
                } else {
                    mFutureTextViewWidth - paddingLeft - paddingRight
                }
            } else {
                width - paddingLeft - paddingRight
            }
        }
        mTextPaint = paint
        mTextLineCount = -1
        val tmpSSb = SpannableStringBuilder(mOrigText)
        tmpSSb.append(getContentOfString(mGapToExpandHint))
        if (imgSpan1 != null) {
            tmpSSb.append("+")
            tmpSSb.setSpan(
                imgSpan1,
                tmpSSb.length - 1,
                tmpSSb.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
        mLayout = null
        mLayout = DynamicLayout(
            tmpSSb,
            mTextPaint!!,
            mLayoutWidth,
            Layout.Alignment.ALIGN_NORMAL,
            1.0f,
            0.0f,
            false
        )
        mTextLineCount = mLayout!!.lineCount
        if (mTextLineCount <= mMaxLinesOnShrink) {
            return tmpSSb
        }
        val indexEnd = validLayout.getLineEnd(mMaxLinesOnShrink - 1)
        val indexStart = validLayout.getLineStart(mMaxLinesOnShrink - 1)
        var indexEndTrimmed = (indexEnd
                - getLengthOfString(mEllipsisHint)
                - getLengthOfString(mGapToExpandHint))
        if (indexEndTrimmed <= indexStart) {
            indexEndTrimmed = indexEnd
        }
        val remainWidth = validLayout.width - (mTextPaint!!.measureText(
            mOrigText!!.subSequence(indexStart, indexEndTrimmed).toString()
        ) + 0.5).toInt() - (bitmap1?.width ?: 0)
        val widthTailReplaced = mTextPaint!!.measureText(
            getContentOfString(mEllipsisHint)
                    + getContentOfString(mGapToExpandHint)
        )
        var indexEndTrimmedRevised = indexEndTrimmed
        if (remainWidth > widthTailReplaced) {
            var extraOffset = 0
            var extraWidth = 0
            while (remainWidth > widthTailReplaced + extraWidth) {
                extraOffset++
                extraWidth = if (indexEndTrimmed + extraOffset <= mOrigText!!.length) {
                    (mTextPaint!!.measureText(
                        mOrigText!!.subSequence(indexEndTrimmed, indexEndTrimmed + extraOffset)
                            .toString()
                    ) + 0.5).toInt()
                } else {
                    break
                }
            }
            indexEndTrimmedRevised += extraOffset - 1
        } else {
            var extraOffset = 0
            var extraWidth = 0
            while (remainWidth + extraWidth < widthTailReplaced) {
                extraOffset--
                extraWidth = if (indexEndTrimmed + extraOffset > indexStart) {
                    (mTextPaint!!.measureText(
                        mOrigText!!.subSequence(
                            indexEndTrimmed + extraOffset,
                            indexEndTrimmed
                        ).toString()
                    ) + 0.5).toInt()
                } else {
                    break
                }
            }
            indexEndTrimmedRevised += extraOffset
        }
        val fixText = removeEndLineBreak(mOrigText!!.subSequence(0, indexEndTrimmedRevised))
        val ssbShrink = SpannableStringBuilder(fixText)
        if (mEllipsisHint != null) {
            ssbShrink.append(mEllipsisHint)
        }
        ssbShrink.append(getContentOfString(mGapToExpandHint))
        if (imgSpan1 != null) {
            ssbShrink.append("+")
            ssbShrink.setSpan(
                imgSpan1,
                ssbShrink.length - 1,
                ssbShrink.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
        return ssbShrink
    }

    private fun removeEndLineBreak(text: CharSequence): String {
        var str = text.toString()
        while (str.endsWith("\n")) {
            str = str.substring(0, str.length - 1)
        }
        val mLayout: Layout = DynamicLayout(
            str,
            mTextPaint!!,
            mLayoutWidth,
            Layout.Alignment.ALIGN_NORMAL,
            1.0f,
            0.0f,
            false
        )
        if (mLayout.lineCount > mMaxLinesOnShrink) {
            if (str.contains("\n")) {
                str = str.substring(0, str.lastIndexOf("\n"))
            }
        }
        return str
    }

    private val validLayout: Layout
        get() = if (mLayout != null) mLayout!! else layout

    override fun setText(text: CharSequence, type: BufferType) {
        mOrigText = text
        mBufferType = type
        setTextInternal(fixTextInternal(), type)
        post {
            setTextInternal(fixTextInternal(), mBufferType)
            alpha = 1f
        }
    }

    private fun setTextInternal(text: CharSequence?, type: BufferType) {
        super.setText(text, type)
    }

    private fun getLengthOfString(string: String?): Int {
        return string?.length ?: 0
    }

    private fun getContentOfString(string: String?): String {
        return string ?: ""
    }

    internal class CenteredImageSpan(drawableRes: Drawable) : ImageSpan(
        drawableRes
    ) {
        override fun draw(
            canvas: Canvas, text: CharSequence,
            start: Int, end: Int, x: Float,
            top: Int, y: Int, bottom: Int, paint: Paint
        ) {
            val b = drawable
            val fm = paint.fontMetricsInt
            val transY = ((y + fm.descent + y + fm.ascent) / 2 - b.bounds.bottom / 2)
            canvas.save()
            canvas.translate(x, transY.toFloat())
            b.draw(canvas)
            canvas.restore()
        }
    }

}
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="EllipsisIconTextView">
        <attr name="maxLinesOnShrink" format="reference|integer" />
        <attr name="ellipsisHint" format="reference|string" />
        <attr name="gapToExpandHint" format="reference|string" />
        <attr name="ellipsisIcon" format="reference"/>
        <attr name="ellipsisIconAlign" format="boolean"/>
        <attr name="ellipsisIconWidth" format="dimension"/>
        <attr name="ellipsisIconHeight" format="dimension"/>
    </declare-styleable>

</resources>
  • 测试代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.mask_boy.test.myapplication.EllipsisIconTextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="40dp"
        android:gravity="center"
        android:text="My name is Masked Boy, My name is Masked Boy"
        android:textSize="18sp"
        app:ellipsisIconAlign="true"
        app:ellipsisIconHeight="15dp"
        app:ellipsisIconWidth="15dp"
        app:ellipsisHint="..."
        app:gapToExpandHint="More"
        app:layout_constraintBottom_toTopOf="@+id/ellipsisIconTextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:maxLinesOnShrink="1" />

    <com.mask_boy.test.myapplication.EllipsisIconTextView
        android:id="@+id/ellipsisIconTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="40dp"
        android:gravity="center"
        android:text="My name is Masked Boy, My name is Masked Boy"
        android:textSize="18sp"
        app:ellipsisIcon="@drawable/ic_lock_tips_arrow"
        app:ellipsisIconAlign="true"
        app:ellipsisIconHeight="15dp"
        app:ellipsisIconWidth="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:maxLinesOnShrink="2" />

    <com.mask_boy.test.myapplication.EllipsisIconTextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="40dp"
        android:gravity="center"
        android:text="My name is Masked Boy, My name is Masked Boy"
        android:textSize="18sp"
        app:ellipsisIcon="@drawable/ic_lock_tips_arrow"
        app:ellipsisIconAlign="true"
        app:ellipsisIconHeight="15dp"
        app:ellipsisIconWidth="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ellipsisIconTextView"
        app:maxLinesOnShrink="1" />
</androidx.constraintlayout.widget.ConstraintLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多线程篇-8--线程安全(死锁,常用保障安全的方法,安全容器,原子类,Fork/Join框架等)

1、线程安全和不安全定义 &#xff08;1&#xff09;、线程安全 线程安全是指一个类或方法在被多个线程访问的情况下可以正确得到结果&#xff0c;不会出现数据不一致或其他错误行为。 线程安全的条件 1、原子性&#xff08;Atomicity&#xff09; 多个操作要么全部完成&a…

Day1 生信新手笔记

生信新手笔记 生信学习第一天笔记打卡。 转录组学中&#xff1a; 上游分析-基于linux&#xff0c;包括质控、过滤、比对、定量&#xff1b; 下游分析-基于R语言&#xff0c;包括差异分析、富集分析、可视化。 1. 级别标题 一个井号加空格 就是一级标题&#xff0c;两个井号加…

Git远程仓库操作

文章目录 远程仓库连接Gitee克隆代码 多人协同问题说明 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Git专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月1日13点10分 远程仓库 Git 是分布式版本控制系统&#xff0c;同一个 Git …

virtualbox给Ubuntu22创建共享文件夹

1.在windows上的操作&#xff0c;创建共享文件夹Share 2.Ubuntu22上的操作&#xff0c;创建共享文件夹LinuxShare 3.在virtualbox虚拟机设置里&#xff0c;设置共享文件夹 共享文件夹路径&#xff1a;选择Windows系统中你需要共享的文件夹 共享文件夹名称&#xff1a;挂载至wi…

人工智能-深度学习-BP算法

BP算法的核心思想是通过计算损失函数对网络参数的梯度&#xff0c;然后使用梯度下降法来更新网络参数&#xff0c;从而最小化损失函数。 误差反向传播算法(BP)的基本步骤: 前向传播&#xff1a;正向计算得到预测值。 计算损失&#xff1a;通过损失函数计算预测值和真实值的差…

(免费送源码)计算机毕业设计原创定制:Apache+JSP+Ajax+Springboot+MySQL Springboot自习室在线预约系统

摘 要 远程预约是一种全新的网络租用方式&#xff0c;它通过互联网突破了时间和空间限制&#xff0c;实现了便捷快速的预约与管理功能。在对数据信息有效组织并整合了一定使用功能后&#xff0c;远程预约系统可以方便地实现预约与取消&#xff0c;以及信息查询等功能。经过本人…

【51单片机】程序实验910.直流电机-步进电机

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 前置知识&#xff1a;C语言 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 码字不易&#xff0c;求点赞收藏加关注(•ω•̥) 有问题欢迎评论区讨论~ 目录 程序实验9&10.直流电机-步进电机…

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环&#xff0c;网络上也有很多 UI 自动化相关的知识或资料&#xff0c;具体到 windows 端的 UI 自动化&#xff0c;我们需要从以下几个方面考虑&#xff1a; 开发语言 毋庸置疑&#xff0c;在 UI 自动化测试领域&am…

我不是挂王-用python实现燕双鹰小游戏

一.准备工作 1.前言提要 作为程序员在浩瀚的数字宇宙中&#xff0c;常常感觉现实世界是一台精密运作的虚拟机&#xff0c;其底层的物理逻辑如同铁律般难以撼动。然而我们拥有在虚拟世界中自由驰骋、创造无限可能的独特力量。突发奇我想用Python写出燕双鹰的小游戏,这样想想就很…

会议直击|美格智能亮相2024紫光展锐全球合作伙伴大会,融合5G+AI共拓全球市场

11月26日&#xff0c;2024紫光展锐全球合作伙伴大会在上海举办&#xff0c;作为紫光展锐年度盛会&#xff0c;吸引来自全球的众多合作伙伴和行业专家、学者共同参与。美格智能与紫光展锐竭诚合作多年&#xff0c;共同面向5G、AI和卫星通信为代表的前沿科技&#xff0c;聚焦技术…

3. STM32_串口

数据通信的基础概念 什么是串行/并行通信&#xff1a; 串行通信就是数据逐位按顺序依次传输 并行通信就是数据各位通过多条线同时传输。 什么是单工/半双工/全双工通信&#xff1a; 单工通信&#xff1a;数据只能沿一个方向传输 半双工通信&#xff1a;数据可以沿两个方向…

RPC与HTTP调用模式的架构差异

RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;和 HTTP 调用是两种常见的通信模式&#xff0c;它们在架构上有以下一些主要差异&#xff1a; 协议层面 RPC&#xff1a;通常使用自定义的二进制协议&#xff0c;对数据进行高效的序列化和反序列化&am…

Microsoft Excel如何插入多行

1.打开要编辑的excel表&#xff0c;在指定位置&#xff0c;鼠标右键点击“插入”一行 2.按住shift键&#xff0c;鼠标的光标箭头会变化成如下图所示 3.一直按住shift键和鼠标左键&#xff0c;往下拖动&#xff0c;直至到插入足够的行

【python】图像、音频、视频等文件数据采集

【python】图像、音频、视频等文件数据采集 先安装所需要的工具一、Tesseract-OCRTesseract-OCR环境变量设置验证是否配置成功示例语言包下载失败 二、ffmpeg验证是否安装成功示例 先安装所需要的工具 一、Tesseract-OCR Tesseract是一个 由HP实验室开发 由Google维护的开源的…

虚拟机docker记录

最近看了一个up的这个视频&#xff0c;感觉docker真的挺不错的&#xff0c;遂也想来搞一下&#xff1a; https://www.bilibili.com/video/BV1QC4y1A7Xi/?spm_id_from333.337.search-card.all.click&vd_sourcef5fd730321bc0e9ca497d98869046942 这里我用的是vmware安装ubu…

C++STL之vector(超详细)

CSTL之vector 1.vector基本介绍2.vector重要接口2.1.构造函数2.2.迭代器2.3.空间2.3.1.resize2.3.2.capacity 2.4.增删查找 3.迭代器失效4.迭代器分类 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f68…

深度学习实验十三 卷积神经网络(4)——使用预训练resnet18实现CIFAR-10分类

目录 一、数据加载 二、数据集类构建 三、模型构建 四、模型训练 五、模型评价及预测 附完整可运行代码&#xff1a; 实验大体步骤&#xff1a; 注&#xff1a; 在自己电脑的CPU跑代码 连接远程服务器跑代码√ 本次实验由于数据量巨大&#xff0c;我的笔记本上还没有…

【Maven Helper】分析依赖冲突案例

目录 Maven Helper实际案例java文件pom.xml文件运行抛出异常分析 参考资料 《咏鹅》骆宾王 鹅&#xff0c;鹅&#xff0c;鹅&#xff0c;曲项向天歌。 白毛浮绿水&#xff0c;红掌拨清波。 骆宾王是在自己7岁的时候就写下了这首杂言 Maven Helper A must have plugin for wor…

Android 桌面窗口新功能推进,聊一聊 Android 桌面化的未来

Android 桌面化支持可以说是 Android 15 里被多次提及的 new features&#xff0c;例如在 Android 15 QPR1 Beta 2 里就提到为 Pixel 平板引入了桌面窗口支持&#xff0c;桌面窗口允许用户在自由窗口同时运行多个应用&#xff0c;同时可以像在传统 PC 平台上一样调整这些窗口的…

【深度学习】四大图像分类网络之VGGNet

2014年&#xff0c;牛津大学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和Google DeepMind公司一起研发了新的卷积神经网络&#xff0c;并命名为VGGNet。VGGNet是比AlexNet更深的深度卷积神经网络&#xff0c;该模型获得了2014年ILSVRC竞赛的第二名&#xff0c…