Android中TextView限制最大行数并在最后用显示...全文

目录
  • 一、场景
  • 二、方案的实现
    • 1、”常规”方案
    • 2、"优化"的处理方案
    • 3、最终方案
  • 三、完整代码
  • 四、效果
  • 五、代码地址

一、场景

我们知道通常在列表页面会有很多内容,而且每条内容可能会很长,如果每条内容都全部显示用户体验就很不好。所以,我们通常的处理方案是限制每条内容的行数,这个时候如果想更加明显的提示用户该条内容有更多的内容,可以进入详情页查看时会在内容最后加上“全文”之类的字眼。尤其是社区内的APP里经常会看到这样的场景,比如:微博。

二、方案的实现

那如果我们想限制最大行数且在最后显示...全文该怎么实现呢?我们知道我们通常设置TextView的最大行数是设置maxLines属性,并设置android:ellipsize="end"表示在内容最后显示...。但是类似"全文“这样的文字怎么显示呢?我想这时大家肯定会想到:在内容最后拼上去啊!没错,是需要拼上去,那要怎么拼?怎么拼上去正好在内容的最后,既不提前、又完整显示”全文“?

1、”常规”方案

网上大多关于这个需求的实现方案都是在textView.setText()之后调用textView.post方法,伪代码:

textView.post(new Runnable() {
            @Override
            public void run() {
                //进行内容的截取和拼接
            }
     });

或者是设置addOnGlobalLayoutListener监听,伪代码:

textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                    //进行内容的截取和拼接
                }
            }
        });

其本质和核心都是为了获取内容的行数,来判断是否大于我们想设置的最大行数,来进行内容的截取和”全文“的拼接。

但是该方案是在setText()之后进行的截取,也就是TextView已经显示了内容然后再进行内容的处理再次setText()。那么会有以下明显的缺点:

1:在性能差的设备上会有闪现全部内容然后再显示处理后的内容。

2:这样做会有两次的setText()操作,在内容很多的列表页会加大性能的损耗。

2、"优化"的处理方案

这个时候可能有人会说既然绘制完成后再处理会有问题,提前获取到textView的行数进行处理不就好了吗?没错,我们可以设置addOnPreDrawListener监听提前获取行数来进行处理,伪代码:

textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
     @Override
     public boolean onPreDraw() {
        //进行内容的截取和拼接
        return false
    }
});

但是这种方案只适合单独一条内容,不适合在列表中使用,因为这样只有在第一屏有效,且滑动多屏后回到第一屏也会重置为原始数据。

3、最终方案

既然设置addOnPreDrawListener监听提前获取行数来进行处理的方案在列表中不可行还有没有其他方法呢?那当然是在TextView的onMeasure()中测量textView的高度时进行内容的处理,并设置相对应的高度了,这样就可以保证性能问题又能保证列表中的每条内容都能得到处理。先上代码:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    if (lineCount > maxLine) {
        //如果大于设置的最大行数
        val (layout, stringBuilder, sb) = clipContent()
        stringBuilder.append(sb)
        setMeasuredDimension(measuredWidth, getDesiredHeight(layout))
        text = stringBuilder
    }
}

/**
 * 裁剪内容
 */
private fun clipContent(): Triple<Layout, SpannableStringBuilder, SpannableString> {
    var offset = 1
    val layout = layout
    val staticLayout = StaticLayout(
            text,
            layout.paint,
            layout.width,
            Layout.Alignment.ALIGN_NORMAL,
            layout.spacingMultiplier,
            layout.spacingAdd,
            false
    )
    val indexEnd = staticLayout.getLineEnd(maxLine - 1)
    val tempText = text.subSequence(0, indexEnd)
    var offsetWidth =
            layout.paint.measureText(tempText[indexEnd - 1].toString()).toInt()
    val moreWidth =
            ceil(layout.paint.measureText(moreText).toDouble()).toInt()
    //表情字节个数
    var countEmoji = 0
    while (indexEnd > offset && offsetWidth <= moreWidth ) {
        //当前字节是否位表情
        val isEmoji = PublicMethod.isEmojiCharacter(tempText[indexEnd - offset])
        if (isEmoji){
            countEmoji += 1
        }
        offset++
        val pair = getOffsetWidth(
                indexEnd,
                offset,
                tempText,
                countEmoji,
                offsetWidth,
                layout,
                moreWidth
        )
        offset = pair.first
        offsetWidth = pair.second
    }
    val ssbShrink = tempText.subSequence(0, indexEnd - offset)
    val stringBuilder = SpannableStringBuilder(ssbShrink)
    val sb = SpannableString(moreText)
    sb.setSpan(
            ForegroundColorSpan(moreTextColor), 3, sb.length,
            Spanned.SPAN_INCLUSIVE_INCLUSIVE
    )
    //设置字体大小
    sb.setSpan(
            AbsoluteSizeSpan(moreTextSize, true), 3, sb.length,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
    )
    if (moreCanClick){
        //设置点击事件
        sb.setSpan(
                MyClickSpan(context, onAllSpanClickListener), 3, sb.length,
                Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
    }
    return Triple(layout, stringBuilder, sb)
}

private fun getOffsetWidth(
        indexEnd: Int,
        offset: Int,
        tempText: CharSequence,
        countEmoji: Int,
        offsetWidth: Int,
        layout: Layout,
        moreWidth: Int
): Pair<Int, Int> {
    var offset1 = offset
    var offsetWidth1 = offsetWidth
    if (indexEnd > offset1) {
        val text = tempText[indexEnd - offset1 - 1].toString().trim()
        if (text.isNotEmpty() && countEmoji % 2 == 0) {
            val charText = tempText[indexEnd - offset1]
            offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
            //一个表情两个字符,避免截取一半字符出现乱码或者显示不全...全文
            if (offsetWidth1 > moreWidth && PublicMethod.isEmojiCharacter(charText)) {
                offset1++
            }
        }
    } else {
        val charText = tempText[indexEnd - offset1]
        offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
    }
    return Pair(offset1, offsetWidth1)
}

/**
 * 获取内容高度
 */
private fun getDesiredHeight(layout: Layout?): Int {
    if (layout == null) {
        return 0
    }
    val lineTop: Int
    val lineCount = layout.lineCount
    val compoundPaddingTop = compoundPaddingTop + compoundPaddingBottom - lineSpacingExtra.toInt()
    lineTop = when {
        lineCount > maxLine -> {
            //文字行数超过最大行
            layout.getLineTop(maxLine)
        }
        else -> {
            layout.getLineTop(lineCount)
        }
    }
    return (lineTop + compoundPaddingTop).coerceAtLeast(suggestedMinimumHeight)
}

大概思路就是判断内容行数大于我们想要的内容行数时进行内容的裁剪,内容最后显示的文案moreText可以按照需求配置,我们测量出moreText的宽度,从最大行数的最后一个文字向前遍历截取,直至截取文字的宽度大于等于moreText的宽度,然后我们通过使用SpannableString来拼接moreText文案和moreText的点击事件。这里还处理了截取到表情字符的情况,我们知道一个表情两个字符,如果正好截取到表情的一半可以放下moreText就会导致表情变成一个?的乱码。 另外这里,我们设置了moreText的点击事件,那如果textView本身需要设置点击事件怎么办?这个时候就需要处理触摸事件了,代码如下:

    val text = text
    val spannable = Spannable.Factory.getInstance().newSpannable(text)

    if (event.action == MotionEvent.ACTION_DOWN) {
        //手指按下
        onDown(spannable, event)
    }

    if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) {
        //如果有MyLinkClickSpan就走MyLinkMovementMethod的onTouchEvent
        return MyLinkMovementMethod.instance
                .onTouchEvent(this, text as Spannable, event)
    }

    if (event.action == MotionEvent.ACTION_MOVE) {
        //手指移动
        val mClickSpan = getPressedSpan(this, spannable, event)
        if (mPressedSpan != null && mPressedSpan !== mClickSpan) {
            mPressedSpan = null
            Selection.removeSelection(spannable)
        }
    }
    if (event.action == MotionEvent.ACTION_UP) {
        //手指抬起
        onUp(event, spannable)
    }
    return result
}

/**
 * 手指按下逻辑
 */
private fun onDown(spannable: Spannable, event: MotionEvent) {
    //按下时记下clickSpan
    mPressedSpan = getPressedSpan(this, spannable, event)
    if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
        result = true
        Selection.setSelection(
                spannable, spannable.getSpanStart(mPressedSpan),
                spannable.getSpanEnd(mPressedSpan)
        )
    } else {
        result = if (moreCanClick){
            super.onTouchEvent(event)
        }else{
            false
        }
    }
}

/**
 * 手指抬起逻辑
 */
private fun onUp(event: MotionEvent, spannable: Spannable?) {
    result = if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
        (mPressedSpan as MyClickSpan).onClick(this)
        true
    } else {
        if (moreCanClick) {
            super.onTouchEvent(event)
        }
        false
    }
    mPressedSpan = null
    Selection.removeSelection(spannable)
}

/**
 * 设置尾部...全文点击事件
 */
fun setOnAllSpanClickListener(
        onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener
) {
    this.onAllSpanClickListener = onAllSpanClickListener
}

private fun getPressedSpan(
        textView: TextView, spannable: Spannable,
        event: MotionEvent
): ClickableSpan? {
    var mTouchSpan: ClickableSpan? = null

    var x = event.x.toInt()
    var y = event.y.toInt()
    x -= textView.totalPaddingLeft
    x += textView.scrollX
    y -= textView.totalPaddingTop
    y += textView.scrollY
    val layout = layout
    val line = layout.getLineForVertical(y)
    val off = layout.getOffsetForHorizontal(line, x.toFloat())

    val spans: Array<MyClickSpan> =
            spannable.getSpans(
                    off, off,
                    MyClickSpan::class.java
            )
    if (spans.isNotEmpty()) {
        mTouchSpan = spans[0]
    } else {
        val linkSpans = spannable.getSpans(off, off, MyLinkClickSpan::class.java)
        if (linkSpans != null && linkSpans.isNotEmpty()) {
            mTouchSpan = linkSpans[0]
        }
    }
    return mTouchSpan
}

其中 if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) { //如果有MyLinkClickSpan就走MyLinkMovementMethod的onTouchEvent return MyLinkMovementMethod.instance .onTouchEvent(this, text as Spannable, event) }是对链接的兼容处理,如果对这个有疑问请看我的上一篇关于链接描述的文章#Android 仿微博正文链接交互

三、完整代码

class ListMoreTextView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = R.attr.MoreTextViewStyle
) :
        AppCompatTextView(context, attrs, defStyleAttr) {

    /**
     * 最大行数
     */
    private var maxLine: Int

    private val moreTextSize: Int

    /**
     * 尾部更多文字
     */
    private val moreText: String?

    /**
     * 尾部更多文字颜色
     */
    private val moreTextColor: Int

    /**
     * 是否可以点击尾部更多文字
     */
    private val moreCanClick : Boolean

    private var mPaint: Paint? = null

    /**
     * 尾部更多文字点击事件接口回调
     */
    private var onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener? = null

    /**
     * 实现span的点击
     */
    private var mPressedSpan: ClickableSpan? = null
    private var result = false

    init {
        val array = getContext().obtainStyledAttributes(
            attrs,
            R.styleable.ListMoreTextView, defStyleAttr, 0
        )
        maxLine = array.getInt(R.styleable.MoreTextView_more_action_text_maxLines, Int.MAX_VALUE)
        moreText = array.getString(R.styleable.MoreTextView_more_action_text)
        moreTextSize = array.getInteger(R.styleable.MoreTextView_more_action_text_size, 13)
        moreTextColor = array.getColor(R.styleable.MoreTextView_more_action_text_color, Color.BLACK)
        moreCanClick = array.getBoolean(R.styleable.MoreTextView_more_can_click,false)
        array.recycle()
        init()
    }

    private fun init() {
        mPaint = paint
    }

    /**
     * 设置最大行数
     */
    fun setMaxLine (maxLine : Int){
        this.maxLine = maxLine
    }

    /**
     * 使用者主动调用
     * 如果有显示链接需求一定要调用此方法
     */
    fun setMovementMethodDefault() {
        movementMethod = MyLinkMovementMethod.instance
        highlightColor = Color.TRANSPARENT
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (lineCount > maxLine) {
            //如果大于设置的最大行数
            val (layout, stringBuilder, sb) = clipContent()
            stringBuilder.append(sb)
            setMeasuredDimension(measuredWidth, getDesiredHeight(layout))
            text = stringBuilder
        }
    }

    /**
     * 裁剪内容
     */
    private fun clipContent(): Triple<Layout, SpannableStringBuilder, SpannableString> {
        var offset = 1
        val layout = layout
        val staticLayout = StaticLayout(
                text,
                layout.paint,
                layout.width,
                Layout.Alignment.ALIGN_NORMAL,
                layout.spacingMultiplier,
                layout.spacingAdd,
                false
        )
        val indexEnd = staticLayout.getLineEnd(maxLine - 1)
        val tempText = text.subSequence(0, indexEnd)
        var offsetWidth =
                layout.paint.measureText(tempText[indexEnd - 1].toString()).toInt()
        val moreWidth =
                ceil(layout.paint.measureText(moreText).toDouble()).toInt()
        //表情字节个数
        var countEmoji = 0
        while (indexEnd > offset && offsetWidth <= moreWidth ) {
            //当前字节是否位表情
            val isEmoji = PublicMethod.isEmojiCharacter(tempText[indexEnd - offset])
            if (isEmoji){
                countEmoji += 1
            }
            offset++
            val pair = getOffsetWidth(
                    indexEnd,
                    offset,
                    tempText,
                    countEmoji,
                    offsetWidth,
                    layout,
                    moreWidth
            )
            offset = pair.first
            offsetWidth = pair.second
        }
        val ssbShrink = tempText.subSequence(0, indexEnd - offset)
        val stringBuilder = SpannableStringBuilder(ssbShrink)
        val sb = SpannableString(moreText)
        sb.setSpan(
                ForegroundColorSpan(moreTextColor), 3, sb.length,
                Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
        //设置字体大小
        sb.setSpan(
                AbsoluteSizeSpan(moreTextSize, true), 3, sb.length,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        if (moreCanClick){
            //设置点击事件
            sb.setSpan(
                    MyClickSpan(context, onAllSpanClickListener), 3, sb.length,
                    Spanned.SPAN_INCLUSIVE_INCLUSIVE
            )
        }
        return Triple(layout, stringBuilder, sb)
    }

    private fun getOffsetWidth(
            indexEnd: Int,
            offset: Int,
            tempText: CharSequence,
            countEmoji: Int,
            offsetWidth: Int,
            layout: Layout,
            moreWidth: Int
    ): Pair<Int, Int> {
        var offset1 = offset
        var offsetWidth1 = offsetWidth
        if (indexEnd > offset1) {
            val text = tempText[indexEnd - offset1 - 1].toString().trim()
            if (text.isNotEmpty() && countEmoji % 2 == 0) {
                val charText = tempText[indexEnd - offset1]
                offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
                //一个表情两个字符,避免截取一半字符出现乱码或者显示不全...全文
                if (offsetWidth1 > moreWidth && PublicMethod.isEmojiCharacter(charText)) {
                    offset1++
                }
            }
        } else {
            val charText = tempText[indexEnd - offset1]
            offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
        }
        return Pair(offset1, offsetWidth1)
    }

    /**
     * 获取内容高度
     */
    private fun getDesiredHeight(layout: Layout?): Int {
        if (layout == null) {
            return 0
        }
        val lineTop: Int
        val lineCount = layout.lineCount
        val compoundPaddingTop = compoundPaddingTop + compoundPaddingBottom - lineSpacingExtra.toInt()
        lineTop = when {
            lineCount > maxLine -> {
                //文字行数超过最大行
                layout.getLineTop(maxLine)
            }
            else -> {
                layout.getLineTop(lineCount)
            }
        }
        return (lineTop + compoundPaddingTop).coerceAtLeast(suggestedMinimumHeight)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val text = text
        val spannable = Spannable.Factory.getInstance().newSpannable(text)

        if (event.action == MotionEvent.ACTION_DOWN) {
            //手指按下
            onDown(spannable, event)
        }

        if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) {
            //如果有MyLinkClickSpan就走MyLinkMovementMethod的onTouchEvent
            return MyLinkMovementMethod.instance
                    .onTouchEvent(this, text as Spannable, event)
        }

        if (event.action == MotionEvent.ACTION_MOVE) {
            //手指移动
            val mClickSpan = getPressedSpan(this, spannable, event)
            if (mPressedSpan != null && mPressedSpan !== mClickSpan) {
                mPressedSpan = null
                Selection.removeSelection(spannable)
            }
        }
        if (event.action == MotionEvent.ACTION_UP) {
            //手指抬起
            onUp(event, spannable)
        }
        return result
    }

    /**
     * 手指按下逻辑
     */
    private fun onDown(spannable: Spannable, event: MotionEvent) {
        //按下时记下clickSpan
        mPressedSpan = getPressedSpan(this, spannable, event)
        if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
            result = true
            Selection.setSelection(
                    spannable, spannable.getSpanStart(mPressedSpan),
                    spannable.getSpanEnd(mPressedSpan)
            )
        } else {
            result = if (moreCanClick){
                super.onTouchEvent(event)
            }else{
                false
            }
        }
    }

    /**
     * 手指抬起逻辑
     */
    private fun onUp(event: MotionEvent, spannable: Spannable?) {
        result = if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
            (mPressedSpan as MyClickSpan).onClick(this)
            true
        } else {
            if (moreCanClick) {
                super.onTouchEvent(event)
            }
            false
        }
        mPressedSpan = null
        Selection.removeSelection(spannable)
    }

    /**
     * 设置尾部...全文点击事件
     */
    fun setOnAllSpanClickListener(
            onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener
    ) {
        this.onAllSpanClickListener = onAllSpanClickListener
    }

    private fun getPressedSpan(
            textView: TextView, spannable: Spannable,
            event: MotionEvent
    ): ClickableSpan? {
        var mTouchSpan: ClickableSpan? = null

        var x = event.x.toInt()
        var y = event.y.toInt()
        x -= textView.totalPaddingLeft
        x += textView.scrollX
        y -= textView.totalPaddingTop
        y += textView.scrollY
        val layout = layout
        val line = layout.getLineForVertical(y)
        val off = layout.getOffsetForHorizontal(line, x.toFloat())

        val spans: Array<MyClickSpan> =
                spannable.getSpans(
                        off, off,
                        MyClickSpan::class.java
                )
        if (spans.isNotEmpty()) {
            mTouchSpan = spans[0]
        } else {
            val linkSpans = spannable.getSpans(off, off, MyLinkClickSpan::class.java)
            if (linkSpans != null && linkSpans.isNotEmpty()) {
                mTouchSpan = linkSpans[0]
            }
        }
        return mTouchSpan
    }
}
<declare-styleable name="ListMoreTextView">
    <attr name="more_action_text_maxLines" format="integer"/>
    <attr name="more_action_text" format="string"/>
    <attr name="more_action_text_color" format="color"/>
    <attr name="more_action_text_size" format="integer"/>
    <attr name="more_can_click" format="boolean"/>
</declare-styleable>

注意:如果是有链接需求要主动调用该方法,否则链接的触摸交互无效。

/**
 * 使用者主动调用
 * 如果有显示链接需求一定要调用此方法
 */
fun setMovementMethodDefault() {
    movementMethod = MyLinkMovementMethod.instance
    highlightColor = Color.TRANSPARENT
}

另外,这里没有对内容连续换行的处理,因为个人觉得列表数据是对主要内容的显示,另外客户端不要做太多的数据处理的耗时操作,应该是由后端的同学或者产品设计时避免这种情况的产生。

四、效果

五、代码地址

点击获取

作者:笑慢
链接:https://juejin.cn/post/7037416456782348295
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(0)

相关推荐

  • Android给布局、控件加阴影效果的示例代码

    增加阴影效果,让控件或者布局看起来有立体的效果,总的来说有两种解决方案. 1,直接使用属性: android:elevation="4dp"这样一句代码,就实现了效果,elevation表示海拔,就是布局的z轴的高度,调整高度,可以选择阴影的轻重. <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight=&

  • Android开发手册TextView控件及阴影效果实现

    目录 实践过程 初识 文字阴影 TextView是Android中最简单也是最常见的控件.今天小空就带大家会会她. 实践过程 初识 经过前两篇常用属性和不常用属性的讲解,是不是有些懵了,不要慌,真实开发中用到的属性其实连五分之一都到不了. 我们先来创建个基本的文本控件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.a

  • Android使用shape绘制阴影图层阴影效果示例

    最近在项目中用到一个比较有意思的阴影特效 从上面的效果图上可以发现在图片的右.下有一个阴影的特效,通过这个阴影效果明显会使得这个提示文本有一种立体的观感.瞬间高大上有木有? 基于以上UI效果,有两种最基本的实现方式:UI给出阴影底图和程序猿自我实现两种. 在这里UI设计师给出阴影底图的方式我们就不做讨论了,我们来看下程序猿自我实现的方式怎么做. 首先我们来分析一下上面UI效果,我们不难发现其实上图所示的ui效果本质上可以看成两个图层的叠加,那么有的小伙伴就要说了不就是两个图层的叠加嘛,用画笔(p

  • Android自定义控件ImageView实现点击之后出现阴影效果

    今天美工 直接给我一张图片,要我实现图片点击之后有阴影效果,当时想到了ImageButton,随即自己写了个Demo,发现ImageButton继承ImageView 会有一个默认的背景样式,而且在布局中设计src(前景) 太丑,于是自己写了个自定义控件ImageView实现了点击之后,点击区域有一个灰色的阴影效果,如下: 添加监听回调等; 布局文件代码: <?xml version="1.0" encoding="utf-8"?> <Linear

  • Android中TextView动态设置缩进距离的方法

    需求是需要在TextView前端加入一个标签展示. 最终效果图如下: 根据效果图,很容易就能想到使用SpannableStringBuilder,在这里使用到的就是LeadingMarginSpan这个类了. 官方说明: A paragraph style affecting the leading margin. There can be multiple leading margin spans on a single paragraph; they will be rendered in

  • Android中TextView限制最大行数并在最后用显示...全文

    目录 一.场景 二.方案的实现 1.”常规”方案 2."优化"的处理方案 3.最终方案 三.完整代码 四.效果 五.代码地址 一.场景 我们知道通常在列表页面会有很多内容,而且每条内容可能会很长,如果每条内容都全部显示用户体验就很不好.所以,我们通常的处理方案是限制每条内容的行数,这个时候如果想更加明显的提示用户该条内容有更多的内容,可以进入详情页查看时会在内容最后加上“全文”之类的字眼.尤其是社区内的APP里经常会看到这样的场景,比如:微博. 二.方案的实现 那如果我们想限制最大行数

  • Android中TextView实现垂直滚动和上下滚动效果

    布局里面就是两个自定义的TextView,上面的左右滑动的是AutoHorizontalScrollTextView; 下面上下滚动的是AutoVerticalScrollTextView; 上面左右滑动的非常好实现,直接把AutoHorizontalScrollTextView复制到项目中,复制全类名到布局文件中,和系统TextView一样,只需设置文本其他什么都不用设置: 下面垂直滚动的AutoVerticalScrollTextView相比AutoHorizontalScrollTextV

  • Android中TextView显示插入的图片实现方法

    本文实例讲述了Android中TextView显示插入的图片实现方法.分享给大家供大家参考,具体如下: Android系统默认给TextView插入图片提供了三种方式: 1.ImageSpan 2.Html.ImageGetter 3.TextView.setCompoundDrawables(left, top, right, bottom) 1.TextView使用ImageSpan显示图片 ImageSpan span = new ImageSpan(this, R.drawable.ic

  • Android 中TextView的使用imageview被压缩问题解决办法

    Android 中TextView的使用imageview被压缩问题解决办法 看下运行效果图: 今天解bug的时候遇到一个奇怪的问题:listview的item由一个textview和一个imageview组成,父布局是线性水平排列.我的本意是imageview显示相同的图片,textview显示文本,但是运行程序后发现,当某个textview的文本较多时,imageview会被压缩,刚开始没注意,检查代码了好久. 代码示例如下: <!--文本少的item--> <LinearLayou

  • Android中TextView局部变色功能实现

    在做项目的时候,遇到过一行文字有两种颜色.在菜鸟的时候直接会想到用多个TextView来实现.后来自己学的多了就找到了更为简单的方法了. 直接上代码: 方法一: xml代码片段: <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> Java代码片段: TextView tv=

  • Android中TextView自动适配文本大小的几种解决方案

    目录 TextView文本大小自动适配与TextView边距的去除 一.Autosizing的方式(固定宽度) 二.自定义View的方式(固定宽度) 三.使用工具类自行计算(非控件固定宽度) 四.去除TextView的边距 总结 TextView文本大小自动适配与TextView边距的去除 标题太难取了,其实本文主要就是讲如何控制文本大小,让其自动适配宽度,其次我们还需要精准控制Text的高度和宽度间距等属性. 一般我们的布局都是分 match parent 和 wrap content 而他们

  • Android中TextView实现超过固定行数显示“...展开全部”

    前言 大家都知道,如果要让TextView只显示一行,多出部分用省略号代替的话非常容易,只需要为xml文件中的TextView控件添加android:singleLine="true"即可,那么如果我想让它显示多行的时候应该怎么做呢? 废话不多说,先看效果图: 展开前: 展开后: 示例代码: 工具类: /** * 设置textView结尾...后面显示的文字和颜色 * @param context 上下文 * @param textView textview * @param minL

  • Ruby中实现统计文件行数、单词数和字符数

    在Ruby中我们定义一个wc方法,用来统计文件中出现的文本行数.单词数和字符数,ruby代码程序如下: 复制代码 代码如下: def wc(filename)   nline = nword = nchar = 0   File.open(filename) do |io|     io.each_line do |line|       words = line.split(/\s+/).reject{|w| w.empty? }       #本例中使用了split方法分割单词,当行首有空白

  • SQL Server中统计每个表行数的快速方法

    我们都知道用聚合函数count()可以统计表的行数.如果需要统计数据库每个表各自的行数(DBA可能有这种需求),用count()函数就必须为每个表生成一个动态SQL语句并执行,才能得到结果.以前在互联网上看到有一种很好的解决方法,忘记出处了,写下来分享一下. 该方法利用了sysindexes 系统表提供的rows字段.rows字段记录了索引的数据级的行数.解决方法的代码如下: 复制代码 代码如下: select schema_name(t.schema_id) as [Schema], t.na

随机推荐