需求:
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"