Android在TextView后添加view實現(xiàn)展開收起查看更多

效果如下:


bb46d87745dc017c399e4d975421d609.gif

直接上代:

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迫像,用來控制文本展示區(qū)域厨姚,當展開狀態(tài)時超出的文本將可垂直滾動
 * 非長文本內(nèi)容就像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;//收起時最大展示行數(shù)
    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會重新觸發(fā)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那一行菱阵,更協(xié)調(diào)
                        String tmp = text.subSequence(0, text.length() - 2) + "\n" + text.subSequence(text.length() - 2, text.length());
                        setTextAndRefresh(tmp);
                        return;//setText會重新觸發(fā)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罩缴,在這里再執(zhí)行相關(guān)邏輯
        setMoreViewPosition();
        //記得移除蚊逢,不然會一直回調(diào)
        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);
    }
}

以上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市烙荷,隨后出現(xiàn)的幾起案子镜会,更是在濱河造成了極大的恐慌,老刑警劉巖终抽,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戳表,死亡現(xiàn)場離奇詭異,居然都是意外死亡昼伴,警方通過查閱死者的電腦和手機匾旭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來圃郊,“玉大人价涝,你說我怎么就攤上這事〕钟撸” “怎么了色瘩?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逸寓。 經(jīng)常有香客問我居兆,道長,這世上最難降的妖魔是什么竹伸? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任史辙,我火速辦了婚禮,結(jié)果婚禮上佩伤,老公的妹妹穿的比我還像新娘聊倔。我一直安慰自己,他們只是感情好生巡,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布耙蔑。 她就那樣靜靜地躺著,像睡著了一般孤荣。 火紅的嫁衣襯著肌膚如雪甸陌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天盐股,我揣著相機與錄音钱豁,去河邊找鬼。 笑死疯汁,一個胖子當著我的面吹牛牲尺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谤碳,長吁一口氣:“原來是場噩夢啊……” “哼溃卡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜒简,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘸羡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后搓茬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犹赖,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年卷仑,在試婚紗的時候發(fā)現(xiàn)自己被綠了峻村。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡系枪,死狀恐怖雀哨,靈堂內(nèi)的尸體忽然破棺而出磕谅,到底是詐尸還是另有隱情私爷,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布膊夹,位于F島的核電站衬浑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏放刨。R本人自食惡果不足惜工秩,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望进统。 院中可真熱鬧助币,春花似錦、人聲如沸螟碎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掉分。三九已至俭缓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酥郭,已是汗流浹背华坦。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留不从,地道東北人惜姐。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像椿息,于是被迫代替她去往敵國和親载弄。 傳聞我的和親對象是個殘疾皇子耘拇,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355