帶動畫摺疊TextView,“展開”按鈕與文字同行,且遠離文字

需求:
1、TextView加載文字,如果文字不多餘兩行,直接展示,“展開”按鈕隱藏
2、如果文字大於2行,默認展示2行,在第二行,文字結尾處以 省略號 結尾,且在控件右下角出現“展開”按鈕
3、如果文字展示的最後一行,文字結尾處距離控件終點距離較近,或完整展示N(N>2)行,則,“展開”按鈕在下一行的終點處

效果圖如下:
1、文字不大於2行
在這裏插入圖片描述
2、文字大於2行的默認展示
在這裏插入圖片描述
3、文字終點處,剩餘空間足夠展示 “展開”按鈕
在這裏插入圖片描述
4、文字終點處,剩餘空間不足以展示 “展開”按鈕
在這裏插入圖片描述
在這裏插入圖片描述
實現:以下均爲源碼,直接複製即可使用

:計算過程中,計算的數值,和佈局中的值有關係,所以,請將下面代碼,全部複製,跑起來後,佈局和計算相結合的看。不然,有時候不明白這裏爲什麼 減去15,那裏爲什麼 減去20

attrs.xml文件中

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

    <declare-styleable name="ExpandTVStyle">
        <!--最小行數-->
        <attr name="minLineNum" format="integer"/>
        <!--展開、收縮動畫時間,單位:毫秒-->
        <attr name="expandTime" format="integer"/>
        <!--展開時對應的圖片-->
        <attr name="expandPicID" format="reference"/>
        <!--不展開時對應的圖片-->
        <attr name="notExpandPicID" format="reference"/>
        <!--展開時對應的文字-->
        <attr name="expandTv" format="string"/>
        <!--不展開時對應的文字-->
        <attr name="notExpandTv" format="string"/>
        <!--文字大小,單位:dp-->
        <attr name="tvSize" format="integer"/>
    </declare-styleable>

</resources>

view_expand_tv_layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ScrollView
        android:id="@+id/expand_sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <RelativeLayout
            android:id="@+id/expand_tv_root_rl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#55ff0000">

            <TextView
                android:id="@+id/expand_content_tv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20dp"
                android:visibility="invisible" />

            <TextView
                android:id="@+id/expand_temp_tv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20dp" />


            <LinearLayout
                android:id="@+id/expand_ll"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_alignParentBottom="true"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:visibility="gone">

                <ImageView
                    android:id="@+id/expand_iv"
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:background="@mipmap/ic_launcher" />

                <TextView
                    android:id="@+id/expand_tv"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    tools:text="展開"
                    android:textSize="20dp" />

            </LinearLayout>

        </RelativeLayout>

    </ScrollView>

</LinearLayout>

MyExpandTextView

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;

public class MyExpandTextView extends RelativeLayout {

    private Context mContext;

    private View rootView;

    private ScrollView expand_sv;
    private RelativeLayout expand_tv_root_rl;
    private TextView expand_content_tv;
    private TextView expand_temp_tv;
    private LinearLayout expand_ll;
    private ImageView expand_iv;
    private TextView expand_tv;

    private int expandPicID;
    private int notExpandPicID;

    private String expandTv;
    private String notExpandTv;

    private int minLineNum = 2;
    private int expandTime = 500;

    private int tvSize;

    private TextPaint tp;

    private CharSequence ellipsizeStr;

    private int minHeight;
    private int maxHeight;

    private boolean isExpand = false;//是否展開

    private String string;

    public MyExpandTextView(Context context) {
        this(context, null);
    }

    public MyExpandTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mContext = context;

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandTVStyle);

        minLineNum = ta.getInt(R.styleable.ExpandTVStyle_minLineNum, 2);
        expandTime = ta.getInt(R.styleable.ExpandTVStyle_expandTime, 500);

        expandPicID = ta.getResourceId(R.styleable.ExpandTVStyle_expandPicID, 0);
        notExpandPicID = ta.getResourceId(R.styleable.ExpandTVStyle_notExpandPicID, 0);

        expandTv = ta.getString(R.styleable.ExpandTVStyle_expandTv);
        notExpandTv = ta.getString(R.styleable.ExpandTVStyle_notExpandTv);

        tvSize = ta.getInt(R.styleable.ExpandTVStyle_tvSize, 0);

        ta.recycle();

        handleData();

    }

    private void handleData() {

        rootView = LayoutInflater.from(mContext).inflate(R.layout.view_expand_tv_layout, this);

        expand_sv = rootView.findViewById(R.id.expand_sv);
        expand_tv_root_rl = rootView.findViewById(R.id.expand_tv_root_rl);
        expand_content_tv = rootView.findViewById(R.id.expand_content_tv);
        expand_temp_tv = rootView.findViewById(R.id.expand_temp_tv);
        expand_ll = rootView.findViewById(R.id.expand_ll);
        expand_iv = rootView.findViewById(R.id.expand_iv);
        expand_tv = rootView.findViewById(R.id.expand_tv);

        int textSize = dp2px(mContext, tvSize);

        //初始化畫筆,用於後續文字測量使用
        tp = new TextPaint();
        tp.setTextSize(textSize);

        expand_iv.setBackgroundResource(expandPicID);
        expand_tv.setText(expandTv);

    }


    public void setContent(String str, int mLeft, int mRight) {

        if (TextUtils.isEmpty(str)) {
            return;
        }

        string = str;

        //一行的最大寬度。佈局中,左右各有20dp的間距
        float oneLineWidth = getScreenWidth(mContext) - dp2px(mContext, mLeft) - dp2px(mContext, mRight);

        //全部文字的寬度
        float textLength = tp.measureText(string);

        //行數
        int lineNum = (int) Math.ceil(textLength / oneLineWidth);

        if (lineNum <= 2) {
            //最多2行,說明沒有後續操作了
            expand_ll.setVisibility(View.GONE);
            expand_temp_tv.setVisibility(View.GONE);
            expand_content_tv.setVisibility(View.VISIBLE);
            expand_content_tv.setText(string);
        } else {
            //比2行多,要做特殊處理了


            //注意 ll 佈局中的內容,iv的寬是15dp,參考佈局
            int ivWidth = dp2px(mContext, 15);

            float f2 = tp.measureText(expandTv);

            float ll_width = ivWidth + f2;

			//這裏最後減去的20dp,是爲了文字和"展示"分開
            float availableTextWidth = oneLineWidth * 2 - ll_width - dp2px(mContext, 20);
            ellipsizeStr = TextUtils.ellipsize(string, tp, availableTextWidth, TextUtils.TruncateAt.END);


            if (textLength + ll_width > oneLineWidth * lineNum) {
                //文字寬度+展開佈局的寬度 > 一行最大展示寬度*行數

                string += "\n";

            }
            expand_content_tv.setText(string);
            expand_content_tv.setMaxLines(minLineNum);

            expand_content_tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    //一般用完之後,立即移除該監聽
                    expand_content_tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);

                    //上面設置了最大2行,這裏就是獲取到了最小高度
                    minHeight = expand_content_tv.getMeasuredHeight();

                    //放開行數限制,運行文字全部展示
                    expand_content_tv.setMaxLines(Integer.MAX_VALUE);

                    Log.e("time 1", System.currentTimeMillis() + "");

                    expand_content_tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override
                        public void onGlobalLayout() {
                            //一般用完之後,立即移除該監聽
                            expand_content_tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                            //獲取文字總高度
                            maxHeight = expand_content_tv.getMeasuredHeight();

                            expand_ll.setVisibility(View.VISIBLE);

                            expand_tv_root_rl.getLayoutParams().height = minHeight;
                            expand_content_tv.getLayoutParams().height = minHeight;

                            expand_tv_root_rl.requestLayout();
                            Log.e("time 2", System.currentTimeMillis() + "");
                        }
                    });
                }
            });

			//TODO
            //TODO
            //TODO
            expand_temp_tv.setVisibility(View.VISIBLE);
            expand_temp_tv.setText(ellipsizeStr);

        }

        expand_ll.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                ValueAnimator desAnimator = null;
                if (isExpand) {
                    //做收縮動畫
                    desAnimator = ValueAnimator.ofInt(maxHeight, minHeight);
                } else {
                    //做展開動畫
                    desAnimator = ValueAnimator.ofInt(minHeight, maxHeight);
                }
                desAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animator) {
                        int currentHeight = (Integer) animator.getAnimatedValue();
                        expand_tv_root_rl.getLayoutParams().height = currentHeight;
                        expand_content_tv.getLayoutParams().height = currentHeight;
                        expand_tv_root_rl.requestLayout();

                        //只有展開動畫的時候才需要內容向上滾動,收縮動畫的時候是不需要滾動的
                        if (!isExpand) {
                            int scrollY = currentHeight - minHeight;
                            expand_sv.scrollBy(0, scrollY);
                        }
                    }
                });
                desAnimator.setDuration(expandTime);
                desAnimator.addListener(new DesAnimListener());
                desAnimator.start();

            }
        });


    }

    /**
     * 描述區域動畫的監聽
     *
     * @author Administrator
     */
    private class DesAnimListener implements Animator.AnimatorListener {
        @Override
        public void onAnimationCancel(Animator arg0) {

        }

        @Override
        public void onAnimationEnd(Animator arg0) {

            isExpand = !isExpand;
            if (isExpand) {
                //完全展開了
                expand_content_tv.setText(string);
                expand_iv.setBackgroundResource(notExpandPicID);
                expand_tv.setText(notExpandTv);
            } else {
                //完全收縮回來了
                expand_content_tv.setText(ellipsizeStr);
                expand_iv.setBackgroundResource(expandPicID);
                expand_tv.setText(expandTv);
            }

        }

        @Override
        public void onAnimationRepeat(Animator arg0) {
        }

        @Override
        public void onAnimationStart(Animator arg0) {

            expand_temp_tv.setVisibility(View.GONE);
            expand_content_tv.setVisibility(View.VISIBLE);
            expand_content_tv.setText(string);

        }
    }


    private int dp2px(Context context, float dpValue) {
        final float densityScale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * densityScale + 0.5f);
    }

    public static float getScreenWidth(Context context) {
        return getDisplayMetrics(context).widthPixels;
    }

    public static DisplayMetrics getDisplayMetrics(Context context) {
        DisplayMetrics displaymetrics = new DisplayMetrics();
        ((WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(
                displaymetrics);
        return displaymetrics;
    }


}

以上,就是核心代碼了,接下來,就是簡單是使用了
activity_main.xml

    <com.demo.textdemo.MyExpandTextView
        android:id="@+id/my_expand_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        app:minLineNum = "2"
        app:expandTime = "500"
        app:expandPicID = "@mipmap/ic_find_screen_down"
        app:notExpandPicID = "@mipmap/ic_find_screen_up"
        app:expandTv = "展開"
        app:notExpandTv = "收縮"
        app:tvSize = "20"
        />

界面中

my_expand_tv= findViewById(R.id.my_expand_tv);

//要展示的文字內容
str = "啊哈啊哈啊哈啊哈哈哈啊哈啊哈哈哈啊哈啊哈哈哈哈哈";

my_expand_tv.setContent(str,20,20);

注意看上面最後一句

my_expand_tv.setContent(str,20,20);

後面的2個20,是因爲,在 activity_main.xml 中,有這麼兩句

 android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"

最後,解答一個疑惑,爲什麼,要設置2個TextView,一個展示真的內容,一個temp臨時展示。(詳見:view_expand_tv_layout.xml)

回到 MyExpandTextView ,去看3個 //TODO 的位置,假設,我將TODO下面的2句話,變爲一句

expand_content_tv.setText(ellipsizeStr);

那麼,點擊展開,將失效。

替換後,模擬情況爲:
點擊“展開”前
在這裏插入圖片描述
點擊“展開”後
在這裏插入圖片描述
會變成這樣。所以,只能用一個臨時的,先替代真實的TextView展示,而,真實的,先

android:visibility="invisible"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章