這個(gè)自定義TextView可以實(shí)現(xiàn)內(nèi)部文字的折疊和擴(kuò)展顯示,效果如下:
有興趣的同學(xué)可以自行點(diǎn)擊下載源碼浦夷,里面的注釋已經(jīng)寫(xiě)的很完善了益涧。這里我將我的代碼整理分享出來(lái):
首先需要定義ExpandableTextView的屬性資源文件
attrs.xml
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextViewExpandable">
<attr name="tvea_expandLines" format="integer"/>
<attr name="tvea_shrinkBitmap" format="reference" />
<attr name="tvea_expandBitmap" format="reference" />
<attr name="tvea_textStateColor" format="color" />
<attr name="tvea_textContentColor" format="color" />
<attr name="tvea_textContentSize" format="dimension" />
<attr name="tvea_textShrink" format="string" />
<attr name="tvea_textExpand" format="string" />
</declare-styleable>
</resources>
各屬性的意義通過(guò)名稱大家應(yīng)該也都能理解出來(lái)软瞎,論學(xué)好英語(yǔ)的重要性啊......扯遠(yuǎn)了闯割,接下來(lái)需要給ExpandableTextView指定布局文件layout_textview_expand_animation.xml
代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_text_expand_animation_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_expand_text_view_animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/color_gray_light_content_text"
android:textSize="@dimen/sp_txt_size_content"
tools:text="@string/tips" />
<RelativeLayout
android:id="@+id/rl_expand_text_view_animation_toggle_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<ImageView
android:id="@+id/iv_expand_text_view_animation_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
/>
<TextView
android:id="@+id/tv_expand_text_view_animation_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/iv_expand_text_view_animation_toggle"
android:textColor="@color/colorPrimary"
android:textSize="@dimen/sp_txt_size_content"
tools:text="全部" />
<View
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_toLeftOf="@id/tv_expand_text_view_animation_hint"
android:background="@color/color_divider_line_gray" />
</RelativeLayout>
</LinearLayout>
好接下來(lái)就是我們的主題了,ExpandableTextView類的代碼編寫(xiě)了省店。代碼如下嚣崭,我已經(jīng)將需要注釋的地方都進(jìn)行了注釋。
public class TextViewExpandable extends LinearLayout implements View.OnClickListener{
/**
* TextView
*/
private TextView textView;
/**
* 收起/全部TextView
* <br>shrink/expand TextView
*/
private TextView tvState;
/**
* 點(diǎn)擊進(jìn)行折疊/展開(kāi)的圖片
* <br>shrink/expand icon
*/
private ImageView ivExpandOrShrink;
/**
* 底部是否折疊/收起的父類布局
* <br>shrink/expand layout parent
*/
private RelativeLayout rlToggleLayout;
/**
* 提示折疊的圖片資源
* <br>shrink drawable
*/
private Drawable drawableShrink;
/**
* 提示顯示全部的圖片資源
* <br>expand drawable
*/
private Drawable drawableExpand;
/**
* 全部/收起文本的字體顏色
* <br>color of shrink/expand text
*/
private int textViewStateColor;
/**
* 展開(kāi)提示文本
* <br>expand text
*/
private String textExpand;
/**
* 收縮提示文本
* <br>shrink text
*/
private String textShrink;
/**
* 是否折疊顯示的標(biāo)示
* <br>flag of shrink/expand
*/
private boolean isShrink = false;
/**
* 是否需要折疊的標(biāo)示
* <br>flag of expand needed
*/
private boolean isExpandNeeded = false;
/**
* 是否初始化TextView
* <br>flag of TextView Initialization
*/
private boolean isInitTextView = true;
/**
* 折疊顯示的行數(shù)
* <br>number of lines to expand
*/
private int expandLines;
/**
* 文本的行數(shù)
* <br>Original number of lines
*/
private int textLines;
/**
* 顯示的文本
* <br>content text
*/
private CharSequence textContent;
/**
* 顯示的文本顏色
* <br>content color
*/
private int textContentColor;
/**
* 顯示的文本字體大小
* <br>content text size
*/
private float textContentSize;
/**
* 動(dòng)畫(huà)線程
* <br>thread
*/
private Thread thread;
/**
* 動(dòng)畫(huà)過(guò)度間隔
* <br>animation interval
*/
private int sleepTime = 22;
/**
* handler信號(hào)
* <br>handler signal
*/
private final int WHAT = 2;
/**
* 動(dòng)畫(huà)結(jié)束信號(hào)
* <br>animation end signal of handler
*/
private final int WHAT_ANIMATION_END = 3;
/**
* 動(dòng)畫(huà)結(jié)束懦傍,只是改變圖標(biāo)雹舀,并不隱藏
* <br>animation end and expand only,but not disappear
*/
private final int WHAT_EXPAND_ONLY = 4;
public TextViewExpandable(Context context) {
this(context, null);
}
public TextViewExpandable(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextViewExpandable(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initValue(context, attrs);
initView(context);
initClick();
}
private void initValue(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextViewExpandable);
expandLines = typedArray.getInteger(
R.styleable.TextViewExpandable_tvea_expandLines, 6);
drawableShrink = typedArray.getDrawable(
R.styleable.TextViewExpandable_tvea_shrinkBitmap);
drawableExpand = typedArray
.getDrawable(R.styleable.TextViewExpandable_tvea_expandBitmap);
// 設(shè)置右下角顯示狀態(tài)的文字顏色
textViewStateColor = typedArray.getColor(
R.styleable.TextViewExpandable_tvea_textStateColor, ContextCompat.getColor(context, R.color.colorPrimary));
textShrink = typedArray.getString(R.styleable.TextViewExpandable_tvea_textShrink);
textExpand = typedArray.getString(R.styleable.TextViewExpandable_tvea_textExpand);
// 設(shè)置默認(rèn)值
if (drawableShrink == null) {
// 支持包的獲取Drawable資源的方法
drawableShrink = ContextCompat.getDrawable(context, R.drawable.icon_green_arrow_up);
}
if (drawableExpand == null) {
drawableExpand = ContextCompat.getDrawable(context, R.drawable.icon_green_arrow_down);
}
if (TextUtils.isEmpty(textShrink)) {
textShrink = context.getString(R.string.shrink);
}
if (TextUtils.isEmpty(textExpand)) {
textExpand = context.getString(R.string.expand);
}
textContentColor = typedArray.getColor(
R.styleable.TextViewExpandable_tvea_textContentColor, ContextCompat.getColor(context, R.color.color_gray_light_content_text));
textContentSize = typedArray.getDimension(R.styleable.TextViewExpandable_tvea_textContentSize, 14);
typedArray.recycle();
}
private void initView(Context context) {
// 得到系統(tǒng)的布局解析器
LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflate.inflate(R.layout.layout_textview_expand_animation, this);
rlToggleLayout = (RelativeLayout) layout.findViewById(R.id.rl_expand_text_view_animation_toggle_layout);
textView = (TextView) layout.findViewById(R.id.tv_expand_text_view_animation);
textView.setTextColor(textContentColor);
textView.getPaint().setTextSize(textContentSize);
ivExpandOrShrink = (ImageView) layout.findViewById(R.id.iv_expand_text_view_animation_toggle);
tvState = (TextView) layout.findViewById(R.id.tv_expand_text_view_animation_hint);
tvState.setTextColor(textViewStateColor);
}
/**
* 設(shè)置顯示文本的TextView和顯示底部標(biāo)志的Layout設(shè)置監(jiān)聽(tīng)
*/
private void initClick() {
textView.setOnClickListener(this);
rlToggleLayout.setOnClickListener(this);
}
/**
*
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_expand_text_view_animation:
clickImageToggle();
break;
case R.id.rl_expand_text_view_animation_toggle_layout:
clickImageToggle();
break;
}
}
private void clickImageToggle() {
if (isShrink) {// 如果是折疊狀態(tài)進(jìn)行非折疊處理
doAnimation(expandLines, textLines, WHAT_EXPAND_ONLY);
} else {
doAnimation(textLines, expandLines, WHAT_EXPAND_ONLY);
}
isShrink = !isShrink;
}
/**
* 對(duì)外設(shè)置文本的方法
* @param charSequence:是String的父接口,就是字符序列
*/
public void setText(CharSequence charSequence) {
textContent = charSequence;
// 設(shè)置顯示的TextView顯示文本
textView.setText(charSequence);
// A view tree observer is used to register listeners that can be notified of global changes in the view tree.
// 這是一個(gè)注冊(cè)監(jiān)聽(tīng)視圖樹(shù)的觀察者(observer),在視圖樹(shù)種全局事件改變時(shí)得到通知粗俱。
// 這里指的全局 事件包括而且不局限在以下幾個(gè):整個(gè)視圖樹(shù)的布局變化葱跋,開(kāi)始繪制視圖,觸摸模式改變等等源梭。
ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
// Register a callback to be invoked when the view tree is about to be drawn.
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {// 返回true代表繼續(xù)當(dāng)前繪制,false代表取消
// 判斷該View是否應(yīng)該初始化
if (!isInitTextView) {
return true;
}
// 未初始化進(jìn)行初始化
isInitTextView = false;
// 得到當(dāng)前文本的總行數(shù)
textLines = textView.getLineCount();
// 設(shè)置是否需要顯示擴(kuò)展(總行數(shù)與當(dāng)前顯示的行數(shù)作比較)
isExpandNeeded = textLines > expandLines;
if (isExpandNeeded) {
// 是否啟用折疊標(biāo)志
isShrink = true;
// 調(diào)用動(dòng)畫(huà)
doAnimation(textLines, expandLines, WHAT_ANIMATION_END);
} else {
isShrink = false;
doNotExpand();
}
return true;
}
});
}
/**
* 處理消息
*/
//Indicates that Lint should ignore the specified warnings for the annotated element.
@SuppressLint("HandlerLeak")
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT:
// 不斷修改當(dāng)前TextView的最大行數(shù)
textView.setMaxLines(msg.arg1);
textView.invalidate();
break;
case WHAT_EXPAND_ONLY:
changeExpandState(msg.arg1);
break;
case WHAT_ANIMATION_END:
setExpandState(msg.arg1);
break;
}
}
};
/**
* @param startLines 開(kāi)始動(dòng)畫(huà)的起點(diǎn)行數(shù) <br> start index of animation
* @param endLines 結(jié)束動(dòng)畫(huà)的終點(diǎn)行數(shù) <br> end index of animation
* @param what 動(dòng)畫(huà)結(jié)束后的handler信號(hào)標(biāo)示 <br> signal of animation end
*/
private void doAnimation(final int startLines, final int endLines, final int what) {
new Thread(new Runnable() {
@Override
public void run() {
// 如果起始行大于終止行往上折疊到終止行
if (startLines > endLines) {
int count = startLines;
while (count-- > endLines) {
Message msg = handler.obtainMessage(WHAT, count, 0);
// 休眠一定時(shí)刻
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendMessage(msg);
}
} else if (startLines < endLines) {
// 如果起始行小于終止行向下擴(kuò)展到終止行
int count = startLines;
while (count++ < endLines) {
Message msg = handler.obtainMessage(WHAT, count, 0);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendMessage(msg);
}
}
// 動(dòng)畫(huà)結(jié)束后發(fā)送結(jié)束的信號(hào)
// animation end,send signal
Message msg = handler.obtainMessage(what, endLines, 0);
handler.sendMessage(msg);
}
}).start();
}
/**
* 負(fù)責(zé)改變折疊或展開(kāi)狀態(tài)的方法
* @param endLines
*/
private void changeExpandState(int endLines) {
rlToggleLayout.setVisibility(VISIBLE);
if (endLines > expandLines) {// 顯示展開(kāi)的狀態(tài)
ivExpandOrShrink.setBackground(drawableShrink);
tvState.setText(textShrink);
} else {// 顯示折疊的狀態(tài)
ivExpandOrShrink.setBackground(drawableExpand);
tvState.setText(textExpand);
}
}
/**
* 設(shè)置折疊或展開(kāi)的狀態(tài)方法
* @param endLines
*/
private void setExpandState(int endLines) {
if (endLines < textLines) {// 小于總行數(shù)稍味,設(shè)置為可以折疊或擴(kuò)展?fàn)顟B(tài)
isShrink = true;
rlToggleLayout.setVisibility(VISIBLE);
ivExpandOrShrink.setBackground(drawableExpand);
textView.setOnClickListener(this);
tvState.setText(textExpand);
} else {// 設(shè)置為不顯示折疊狀態(tài)
Log.e("xns", "not show shrink");
isShrink = false;
rlToggleLayout.setVisibility(GONE);
ivExpandOrShrink.setBackground(drawableShrink);
textView.setOnClickListener(null);
tvState.setText(textShrink);
}
}
/**
* 無(wú)需折疊
* do not expand
*/
private void doNotExpand() {
textView.setMaxLines(expandLines);
rlToggleLayout.setVisibility(GONE);
textView.setOnClickListener(null);
}
public Drawable getDrawableShrink() {
return drawableShrink;
}
public void setDrawableShrink(Drawable drawableShrink) {
this.drawableShrink = drawableShrink;
}
public Drawable getDrawableExpand() {
return drawableExpand;
}
public void setDrawableExpand(Drawable drawableExpand) {
this.drawableExpand = drawableExpand;
}
public int getExpandLines() {
return expandLines;
}
public void setExpandLines(int newExpandLines) {
int startLines = isShrink ? expandLines : textLines;
int endLines = newExpandLines > textLines ? newExpandLines : textLines;
doAnimation(startLines, endLines, WHAT_ANIMATION_END);
expandLines = newExpandLines;
}
/**
* 取得顯示的文本內(nèi)容
* get content text
*
* @return content text
*/
public CharSequence getTextContent() {
return textContent;
}
public int getSleepTime() {
return sleepTime;
}
public void setSleepTime(int sleepTime) {
this.sleepTime = sleepTime;
}
}
- 整體設(shè)計(jì)的精髓即是通過(guò)handle接受Message后废麻,不斷的修改內(nèi)部TextView的最大行數(shù),然后調(diào)用該TextView的初始化方法模庐,來(lái)完成動(dòng)畫(huà)的顯示烛愧。
- 這里還涉及到一個(gè)新的知識(shí)點(diǎn)即ViewTreeObserver類,簡(jiǎn)單來(lái)說(shuō)它是一個(gè)監(jiān)聽(tīng)ViewTree的觀察者掂碱,在視圖樹(shù)中全局事件改變時(shí)得到通知怜姿。全局事件包括但不局限于如:整個(gè)視圖樹(shù)的布局變化,開(kāi)始繪制視圖疼燥,觸摸模式改變等沧卢。
ViewTreeObserver是不能被應(yīng)用程序?qū)嵗模驗(yàn)樗怯梢晥D提供的醉者,通過(guò)view.getViewTreeObserver()獲取但狭。
網(wǎng)上有很多關(guān)于這個(gè)類的介紹,這里我推薦一篇博文撬即,你可以大致了解這個(gè)類的一些基本知識(shí)立磁。
其余都是一些邏輯上的代碼,通過(guò)注釋我相信大家都可以讀懂它的實(shí)現(xiàn)方法剥槐。朝陽(yáng)在前方唱歧,同志們繼續(xù)前進(jìn)吧。