美文网首页
Android TextView超过指定限制在文末显示 &quo

Android TextView超过指定限制在文末显示 &quo

作者: 你毛多肉少 | 来源:发表于2017-07-23 10:39 被阅读857次

    最近有个需求就是文字显示指定行数比如3行,超过3行时就在第三行的末尾显示省略号加“全文”。
    效果类似今日头条和微博:

    image.png

    实现如下:
    1、ShowAllTextView 继承TextView类

    public class ShowAllTextView extends TextView {
    
    /**全文按钮点击事件*/
    private ShowAllSpan.OnAllSpanClickListener onAllSpanClickListener;
    private int maxShowLines = 0;  //最大显示行数
    
    public ShowAllTextView(Context context) {
        super(context);
    }
    
    public ShowAllTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    /**调用此方法才有效果*/
    public void setMyText(CharSequence text) {
        super.setText(text);
        post(new Runnable() {
                @Override
                public void run() {
                    addEllipsisAndAllAtEnd();
                }
         });
    }
    
    /**调用此方法才有效果*/
    public void setMyText(int resId){
        setMyText(getContext().getResources().getText(resId));
    }
    
    /**超过规定行数时, 在文末添加 "...全文"*/
    private void addEllipsisAndAllAtEnd(){
        if (maxShowLines > 0 && maxShowLines < getLineCount()) {
            try {
                int moreWidth = PaintUtils.getTheTextNeedWidth(getPaint(), "...全文");
                /**加上...全文 长度超过了textView的宽度, 则多减去5个字符*/
                if (getLayout().getLineRight(maxShowLines - 1) + moreWidth >= getLayout().getWidth()){
                    this.setText(getText().subSequence(0, getLayout().getLineEnd(maxShowLines - 1) - 5));
                    /**避免减5个字符后还是长度还是超出了,这里再减4个字符*/
                    if (getLayout().getLineRight(maxShowLines - 1) + moreWidth >= getLayout().getWidth()){
                        this.setText(getText().subSequence(0, getLayout().getLineEnd(maxShowLines - 1) - 4));
                    }
                }else {
                    this.setText(getText().subSequence(0, getLayout().getLineEnd(maxShowLines - 1)));
                }
                if (getText().toString().endsWith("\n") && getText().length() >= 1){
                    this.setText(getText().subSequence(0, getText().length() - 1));
                }
                this.append("...");
                SpannableString sb = new SpannableString("全文");
                sb.setSpan(new ShowAllSpan(getContext(), onAllSpanClickListener), 0, sb.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                this.append(sb);
            }catch (Exception e){}
        }
    }
    
    public void setOnAllSpanClickListener(ShowAllSpan.OnAllSpanClickListener onAllSpanClickListener) {
        this.onAllSpanClickListener = onAllSpanClickListener;
    }
    
    public int getMaxShowLines() {
        return maxShowLines;
    }
    
    public void setMaxShowLines(int maxShowLines) {
        this.maxShowLines = maxShowLines;
    }
    
    //实现span的点击
    private ClickableSpan mPressedSpan = null;
    private boolean result = false;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        CharSequence text = getText();
        Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPressedSpan = getPressedSpan(this, spannable, event);
                if (mPressedSpan != null){
                    if (mPressedSpan instanceof ShowAllSpan){
                        ((ShowAllSpan) mPressedSpan).setPressed(true);
                    }
                    Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan), spannable.getSpanEnd(mPressedSpan));
                    result = true;
                }else {
                    result = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                ClickableSpan mClickSpan = getPressedSpan(this, spannable, event);
                if (mPressedSpan != null && mPressedSpan != mClickSpan){
                    if (mPressedSpan instanceof ShowAllSpan){
                        ((ShowAllSpan) mPressedSpan).setPressed(false);
                    }
                    mPressedSpan = null;
                    Selection.removeSelection(spannable);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mPressedSpan != null){
                    if (mPressedSpan instanceof ShowAllSpan){
                        ((ShowAllSpan) mPressedSpan).setPressed(false);
                    }
                    mPressedSpan.onClick(this);
                }
                mPressedSpan = null;
                Selection.removeSelection(spannable);
                break;
        }
        return result;
    
    }
    
    private ClickableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) {
    
        ClickableSpan mTouchSpan = null;
    
        int x = (int) event.getX();
        int y = (int) event.getY();
        x -= textView.getTotalPaddingLeft();
        x += textView.getScrollX();
        y -= textView.getTotalPaddingTop();
        y += textView.getScrollY();
        Layout layout = getLayout();
        int line = layout.getLineForVertical(y);
        int off = layout.getOffsetForHorizontal(line, x);
    
        ShowAllSpan[] spans = spannable.getSpans(off, off, ShowAllSpan.class);
        if (spans != null && spans.length > 0){
            mTouchSpan = spans[0];
        }
        return mTouchSpan;
    }
    }
    

    2、ShowAllSpan 可点击的“全文”的span类

    public class ShowAllSpan extends ClickableSpan {
    
    private OnAllSpanClickListener clickListener;
    private boolean isPressed = false;
    private Context context;
    
    public ShowAllSpan(Context context, OnAllSpanClickListener clickListener){
        this.context = context;
        this.clickListener = clickListener;
    }
    
    @Override
    public void onClick(View widget) {
        if (clickListener != null){
            clickListener.onClick(widget);
        }
    }
    
    public void setPressed(boolean pressed) {
        isPressed = pressed;
    }
    
    public interface OnAllSpanClickListener{
        void onClick(View widget);
    }
    
    @Override
    public void updateDrawState(TextPaint ds) {
        if (isPressed){
            ds.bgColor = context.getResources().getColor(android.R.color.darker_gray);
        }else {
            ds.bgColor = context.getResources().getColor(android.R.color.transparent);
        }
        ds.setColor(context.getResources().getColor(android.R.color.holo_blue_light));
        ds.setUnderlineText(false);
    }
    }
    

    3、PaintUtils 画笔工具类

    public class PaintUtils 
    
    /** 计算指定画笔下指定字符串需要的宽度*/
    public static int getTheTextNeedWidth(Paint thePaint, String text) {
        float[] widths = new float[text.length()];
        thePaint.getTextWidths(text, widths);
        int length = widths.length, nowLength = 0;
        for (int i = 0; i < length; i++) {
            nowLength += widths[i];
        }
        return nowLength;
    }
    }
    

    4、测试demo:

    public class ShowAllTextActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_showalltext);
    
        ShowAllTextView tv_1 = (ShowAllTextView) findViewById(R.id.tv_1);
        ShowAllTextView tv_2 = (ShowAllTextView) findViewById(R.id.tv_2);
    
        tv_1.setMaxShowLines(3);
        tv_1.setMyText("Dwayne Jones was a Jamaican 16-year-old who was killed by a violent mob " +
                "in Montego Bay on the night of 21 July 2013, after he attended a dance party " +
                "dressed in women's clothing. Perceived as effeminate, Jones had been bullied " +
                "in school and rejected by his father, and had moved into a derelict house in " +
                "Montego Bay with transgender friends. When some men at the dance party discovered " +
                "that the cross-dressing Jones was not a woman, they confronted and attacked him. " +
                "He was beaten, stabbed, shot, and run over by a car. Police investigated, " +
                "but the murder remains unsolved. The death made news internationally. " +
                "While voices on social media accused Jones of provoking his killers by ");
        tv_1.setOnAllSpanClickListener(new ShowAllSpan.OnAllSpanClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(ShowAllTextActivity.this, "点击了全文1", Toast.LENGTH_SHORT).show();
            }
        });
    
        tv_2.setMaxShowLines(5);
        tv_2.setMyText("水温刚刚合适,服侍我的是那个面目清秀的男孩,他把手慢慢的挪到我两腿之间,抚摸着我白皙的长腿,他小心翼翼的为我脱毛,神情紧张,生怕弄疼了我。\n" +
                "我就这样躺在白砂做的浴缸里,男孩轻轻的在我胸口,腹部均匀的涂抹浴盐,又在我的背部涂抹类似于橄榄油一样的护肤品。\n" +
                "男孩从一旁取出一些瓶瓶罐罐的香料,他把一些类似于花瓣一样的红色的颗粒撒在我的周围,并用纱布把那种名贵的香料挤出汁液淋在我身上。我的身体愈加的酥软,真的太舒服了,男孩的手法娴熟,让我如痴如醉,他不断的在我沐浴的水中添加美白和使我皮肤细腻的香料。\n" +
                "水温有些升高了。\n" +
                "男孩突然把我已经瘫软的身体翻了过来,他分开我的双腿……\n" +
                "他,他拿出了相机,居然在拍照,作为一只鸡,被偷拍是我们这一行的大忌,男孩把照片印在一本花名册上,我打赌那上面一定有很多鸡的照片,可能都是他找过的吧。\n" +
                "可是我却,我却居然有些喜欢上这个男孩了。\n" +
                "他还给我在水中沐浴的照片起了个奇怪的名字:“枸杞炖鸡汤。");
        tv_2.setOnAllSpanClickListener(new ShowAllSpan.OnAllSpanClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(ShowAllTextActivity.this, "点击了全文2", Toast.LENGTH_SHORT).show();
            }
        });
    }
    }
    

    效果如下:

    image.png

    待优化:

    int moreWidth = PaintUtils.getTheTextNeedWidth(getPaint(), "...全文");
                /**加上...全文 长度超过了textView的宽度, 则多减去5个字符*/
     if (getLayout().getLineRight(maxShowLines - 1) + moreWidth >= getLayout().getWidth()){
     this.setText(getText().subSequence(0, getLayout().getLineEnd(maxShowLines - 1) - 5));
     /**避免减5个字符后还是长度还是超出了,这里再减4个字符*/
     if (getLayout().getLineRight(maxShowLines - 1) + moreWidth >= getLayout().getWidth()){
    this.setText(getText().subSequence(0, getLayout().getLineEnd(maxShowLines - 1) - 4));
                    }
                }
    

    判断全文是否超出了本行的逻辑处理比较简单,需求没那么严格,暂时就这样。如果要严格在行末的位置,可以一个一个的减字符,直到可以刚好把“...全文”放下。

    备注:
    代码地址:https://github.com/yang1006/ShowAllTextView

    相关文章

      网友评论

          本文标题:Android TextView超过指定限制在文末显示 &quo

          本文链接:https://www.haomeiwen.com/subject/wvpekxtx.html