欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android在TextView后添加view实现展开收起查看更多

程序员文章站 2022-05-06 21:57:06
import android.content.Context;import android.graphics.drawable.Drawable;import android.text.Layout;import android.util.AttributeSet;import android.view.Gravity;import android.view.MotionEvent;import android.view.ViewGroup;import android.view.ViewT....

效果如下:
android在TextView后添加view实现展开收起查看更多
直接上代码:

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

/**
 * 在超长文本后面加一个view,用来控制文本展示区域,当展开状态时超出的文本将可垂直滚动
 * 非长文本内容就像TextView一样
 *
 * @author ly
 * date 2020/3/9 10:22
 */
public class ScrollExpandTextView extends ScrollView implements ViewTreeObserver.OnGlobalLayoutListener {

    private static final int MAX_LINE_COLLAPSE = 2;//收起时最大展示行数
    private static final String TEXT_EXPAND = "展开";
    private static final String TEXT_COLLAPSE = "收起";
    private static final int MAX_H = 3 * PixelUtil.dp2px(50);
    private int w;
    private int minH;
    private int curH;
    private int arrowSize;
    private TextView tv;
    private TextView tvMore;
    private State state = State.COLLAPSE;
    private CharSequence text;
    private LayoutParams paramsMore;
    private int space;

    public enum State {
        EXPAND, COLLAPSE
    }

    public ScrollExpandTextView(@NonNull Context context) {
        super(context);
        init();
    }

    public ScrollExpandTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ScrollExpandTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(w, curH);//固定宽高
    }

    private void init() {
        w = (int) (0.7 * ScreenUtil.getScreenWidth());
        minH = PixelUtil.dp2px(50);
        curH = minH;
        arrowSize = PixelUtil.dp2px(7);
        space = PixelUtil.dp2px(3);

        FrameLayout flContainer = new FrameLayout(getContext());
        tv = new TextView(getContext());
        tv.setTextSize(14);
        tv.setMaxHeight(PixelUtil.dp2px(50));
        tv.setMaxLines(MAX_LINE_COLLAPSE);
        tv.setIncludeFontPadding(true);
        tv.setLineSpacing(1.0f, 1.2f);
        tvMore = new TextView(getContext());
        tvMore.setBackgroundResource(R.drawable.scroll_expand_tv_more);
        tvMore.setMaxLines(1);
        tvMore.setTextSize(12);
        tvMore.setGravity(Gravity.CENTER);
        tvMore.setVisibility(GONE);
        tvMore.setOnClickListener(v -> {
            if (state == State.COLLAPSE) {
                state = State.EXPAND;
                tv.setMaxLines(Integer.MAX_VALUE);
                tv.setText(text);
            } else {
                state = State.COLLAPSE;
                tv.setMaxLines(MAX_LINE_COLLAPSE);
            }
            setMoreViewPosition();
        });

        tv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        paramsMore = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        tvMore.setLayoutParams(paramsMore);
        flContainer.addView(tv);
        flContainer.addView(tvMore);
        addView(flContainer);

        setTextColor(ContextCompat.getColor(getContext(), R.color.white));
        setMoreTextColor(ContextCompat.getColor(getContext(), R.color.white));

        setOverScrollMode(OVER_SCROLL_NEVER);
        setVerticalScrollBarEnabled(false);
    }

    private void setMoreViewPosition() {
        Layout layout = tv.getLayout();
        if (layout == null)
            return;
        int lineCount = layout.getLineCount();
        int lineH = layout.getLineBottom(0) - layout.getLineTop(0);
        minH = MAX_LINE_COLLAPSE * lineH;
        curH = lineCount * lineH;
        if (text == null || lineCount <= MAX_LINE_COLLAPSE && tv.length() == text.length()) {
            tvMore.setVisibility(GONE);
        } else {
            if (state == State.COLLAPSE) {
                curH = minH;

                float lineWidth = layout.getLineWidth(MAX_LINE_COLLAPSE - 1);
                //获取第2行最后一个字符的下标
                int lineEnd = layout.getLineEnd(MAX_LINE_COLLAPSE - 1);
                //计算每个字符占的宽度
                float widthPerChar = layout.getLineWidth(MAX_LINE_COLLAPSE - 1) / (lineEnd + 1);
                float diff = lineWidth + tvMore.getMeasuredWidth() + space - (getWidth() - getPaddingLeft() - getPaddingRight());
                //第二行展示不下,去掉第二行最后几个字符,用来放展开按钮
                if (diff > 0) {
                    int removeCount = (int) (diff / widthPerChar);
                    if (lineEnd > removeCount) {
                        CharSequence t = text.subSequence(0, lineEnd - removeCount) + "...";
                        setTextAndRefresh(t);
                        return;//setText会重新触发onGlobalLayout
                    }
                }
                //获取第二行字符的坐标,设置展开按钮的margin,使展开按钮在文本后面
                paramsMore.leftMargin = (int) layout.getLineRight(MAX_LINE_COLLAPSE - 1) + space;
                paramsMore.topMargin = lineH + tv.getPaddingTop() - space;

                tvMore.setText(TEXT_EXPAND);
                drawRight4MoreView(R.drawable.ico_arrowdown);
            } else {
                if (curH > MAX_H)
                    curH = MAX_H;

                float lineWidth = layout.getLineWidth(lineCount - 1);
                if (lineWidth + tvMore.getMeasuredWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()) > 0) {//最后一行显示不下,将最后一行换行
                    if (text.length() > 2) {
                        //分两个字符到tvMore那一行,更协调
                        String tmp = text.subSequence(0, text.length() - 2) + "\n" + text.subSequence(text.length() - 2, text.length());
                        setTextAndRefresh(tmp);
                        return;//setText会重新触发onGlobalLayout
                    }
                }
                tvMore.setText(TEXT_COLLAPSE);
                drawRight4MoreView(R.drawable.ico_arrowup);

                paramsMore.leftMargin = (int) layout.getSecondaryHorizontal(layout.getLineEnd(lineCount - 1)) + space;
                paramsMore.topMargin = layout.getHeight() - tv.getPaddingBottom() - lineH + PixelUtil.dp2px(2);
            }
            tvMore.setVisibility(VISIBLE);
        }
        getLayoutParams().height = curH;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        setMoreViewPosition();
    }

    @Override
    public void onGlobalLayout() {
        //为保证TextView.getLayout()!=null,在这里再执行相关逻辑
        setMoreViewPosition();
        //记得移除,不然会一直回调
        tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    public void setText(final CharSequence text) {
        this.text = text;
        setTextAndRefresh(text);
    }

    public void setTextAndRefresh(CharSequence text) {
        tv.getViewTreeObserver().addOnGlobalLayoutListener(this);
        tv.setText(text);
    }

    private void drawRight4MoreView(int icRes) {
        Drawable drawable = getResources().getDrawable(icRes);
        /// 这一步必须要做,否则不会显示.
        drawable.setBounds(arrowSize / 3, 0, arrowSize, arrowSize / 3);
        tvMore.setCompoundDrawables(null, null, drawable, null);
    }

    public void setTextColor(int color) {
        tv.setTextColor(color);
    }

    public void setMoreTextColor(int color) {
        tvMore.setTextColor(color);
    }


	//如果不需要处理滑动冲突,去掉下面的代码即可
    private int startX, startY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) ev.getX();
                startY = (int) ev.getY();
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_MOVE:
                int endX = (int) ev.getX();
                int endY = (int) ev.getY();
                int disX = Math.abs(endX - startX);
                int disY = Math.abs(endY - startY);
                if (disX > disY) {
                    getParent().requestDisallowInterceptTouchEvent(canScrollHorizontally(startX - endX));
                } else {
                    getParent().requestDisallowInterceptTouchEvent(canScrollVertically(startY - endY));
                }
                break;
            default:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

以上

本文地址:https://blog.csdn.net/xiaoerye/article/details/107110495