ExpandableTextView这个库相信大家都用过,没用过的可以先了解下:
https://github.com/Manabu-GT/ExpandableTextView
这里简单介绍下使用方式,主要说的是遇到的问题以及对应的修改,文末会给出重写的ExpandableTextView。
首先ExpandableTextView给我们提供了以下几个可以设置的属性:
maxCollapsedLines :当TextView折叠时允许显示的最大文本行数,默认8
animDuration :textView展开/收缩的动画时间,默认300ms
animAlphaStart :动画启动时TextView的Alpha值(注)如果要禁用alpha 动画,请将此值设置为1。(后面会详细说这个问题)
expandDrawable:textview展开时的图标,这里默认是箭头。
collapseDrawable:textview收缩时的图标,这里默认是箭头。
接着在xml中写入布局:
<!-- sample xml -->
<com.ms.square.android.expandabletextview.ExpandableTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:expandableTextView="http://schemas.android.com/apk/res-auto"
android:id="@+id/expand_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
expandableTextView:maxCollapsedLines="4"
expandableTextView:animDuration="200">
<TextView
android:id="@id/expandable_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textSize="16sp"
android:textColor="#666666" />
<ImageButton
android:id="@id/expand_collapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:layout_gravity="right|bottom"
android:background="@android:color/transparent"/>
</com.ms.square.android.expandabletextview.ExpandableTextView>
注意:这里Textview和ImageButton的id值被事先定义在了ids.xml里面所以必须这么写了。
最后在代码中使用:
ExpandableTextView expTv1 =
(ExpandableTextView)rootView.findViewById(R.id.sample1)
.findViewById(R.id.expand_text_view);
expTv1.setText(getString(R.string.dummy_text1));
通过以上几步就基本能完成一般需求了。如果有的时候我们想要的是带文字说明的,例如:(展开/收起),这样我们也可以自己写布局,这个会在后面说到,这里先了解下基本实现。其实ExpandableTextView的实现是继承自 LinearLayout(默认竖直方向,横向会抛出异常),里面包含一个Textview用于实现文本的显示,一个ImageButton用于显示箭头图标;在设置文本后会测量布局通过比较mTv.getLineCount()和 mMaxCollapsedLines
的大小来进行图标的显隐,以及布局高度的初始化等操作。
下面来说说会遇到的问题:
问题一、
如果你是直接拿demo里面的布局直接用的话,就会发现当文本点击伸缩的时候会出现闪烁的效果;结果当我拿给我们美工看的时候直接被无情的拒回(不知道其他的美工怎么看?)。其实这是一种透明度的动画效果,废话不多说看我们先看源码实现:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void applyAlphaAnimation(View view, float alpha) {
if (isPostHoneycomb()) {
view.setAlpha(alpha);
} else {
AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);
// make it instant
alphaAnimation.setDuration(0);
alphaAnimation.setFillAfter(true);
view.startAnimation(alphaAnimation);
}
}
这里就是根据版本不同来执行不同的透明度变化代码,再来看看调用它的地方,调用的地方一共有两个,第一个在onClick(View view)方法里面的onAnimationStart里面;第二个在ExpandCollapseAnimation这个内部类里面,这是自定义动画,不了解的可以百度下自定义动画。其实这里个人觉得既然在自定义动画里面调用了,那第一个地方根本不用调用了,有点重复。。。
下面看看调用代码:
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) ((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);
if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {
applyAlphaAnimation(mTv, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));
}
mTargetView.getLayoutParams().height = newHeight;
mTargetView.requestLayout();
}
我们可以看到if (Float.compare(mAnimAlphaStart, 1.0f) != 0)
这里有个判断可以决定是否调用透明度变化动画。Float.compare(mAnimAlphaStart, 1.0f)
这个方法作用是比较两个参数的大小:前面<后面 返回 -1;前面 = 后面 返回 0; 前面 > 后面 返回 1; 所以这里如果不想调用动画的话就得等于0;所以只需要设置mAnimAlphaStart的值为1即可;也就是
这个属性。
expandableTextView:animAlphaStart="1"
问题二、
当我们自己添加布局后,会发现如果想文本行数不够预设的值时想隐藏布局,然而ExpandableTextView没有给我们提供判断的方法,这里以在箭头旁边加一个显示的文本为例:
<com.ichsy.whds.common.view.ExpandableTextView
android:id="@+id/tv_personal_instruction"
xmlns:expandableTextView="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
expandableTextView:animDuration="100"
expandableTextView:animAlphaStart="1"
expandableTextView:maxCollapsedLines="6">
<TextView
android:id="@id/expandable_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:lineSpacingExtra="6dp"
android:textColor="@color/color_btn_gray"
android:textSize="@dimen/textsize_13"/>
<RelativeLayout
android:id="@+id/expand_collapse_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@+id/expand_collapse_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="展开全部"
android:textColor="@color/color_standard_3"/>
<ImageButton
android:id="@id/expand_collapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/expand_collapse_tv"
android:background="@android:color/transparent"
android:paddingLeft="4dp"/>
</RelativeLayout>
</com.ichsy.whds.common.view.ExpandableTextView>
这里maxCollapsedLines 设置的是6,当文本不够6的时候会发现箭头隐藏了,但是我们的“展开全部”文本还没有隐藏,这里需要我们自己来扩展提供判断方法了。
查看源码“onMeasure(int widthMeasureSpec, int heightMeasureSpec)” 方法能找到答案:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If no change, measure and return
if (!mRelayout || getVisibility() == View.GONE) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
mRelayout = false;
// Setup with optimistic case
// i.e. Everything fits. No button needed
mButton.setVisibility(View.GONE);
mTv.setMaxLines(Integer.MAX_VALUE);
// Measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If the text fits in collapsed mode, we are done.
if (mTv.getLineCount() <= mMaxCollapsedLines) {
return;
}
// Saves the text height w/ max lines
mTextHeightWithMaxLines = getRealTextViewHeight(mTv);
// Doesn't fit in collapsed mode. Collapse text view as needed. Show
// button.
if (mCollapsed) {
mTv.setMaxLines(mMaxCollapsedLines);
}
mButton.setVisibility(View.VISIBLE);
// Re-measure with new setup
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTv.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
}
});
// Saves the collapsed height of this ViewGroup
mCollapsedHeight = getMeasuredHeight();
}
}
可以看到这行代码
if (mTv.getLineCount() <= mMaxCollapsedLines) {
return;
}
这里通过获取textview的行数和预设的行数做比较,如果小于等于预设值则直接return,下面的代码都不执行了,所以下面button的显示代码也没执行,所以按钮就会隐藏。好了,既然知道了是在这里判断的,那我们提供一个方法就好了。
改写:
private int mTvLineCount;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If no change, measure and return
if (!mRelayout || getVisibility() == View.GONE) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
mRelayout = false;
// Setup with optimistic case
// i.e. Everything fits. No button needed
mButton.setVisibility(View.GONE);
mTv.setMaxLines(Integer.MAX_VALUE);
// Measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If the text fits in collapsed mode, we are done.
mTvLineCount = mTv.getLineCount();
if (mTvLineCount <= mMaxCollapsedLines) {
return;
}
// Saves the text height w/ max lines
mTextHeightWithMaxLines = getRealTextViewHeight(mTv);
// Doesn't fit in collapsed mode. Collapse text view as needed. Show
// button.
if (mCollapsed) {
mTv.setMaxLines(mMaxCollapsedLines);
}
mButton.setVisibility(View.VISIBLE);
// Re-measure with new setup
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTv.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
}
});
// Saves the collapsed height of this ViewGroup
mCollapsedHeight = getMeasuredHeight();
}
}
public boolean isShowExpand() {
return mTvLineCount > mMaxCollapsedLines;
}
当我们happy的以为只要在代码里面调用isShowExpand()
来显隐布局的时候,又会发现这个方法会一直返回 false !!!等等,冷静下来细细一想也对,因为view的绘制需要经过onMeasure
、onLayout
、onDraw
,这之间需要时间,所以这个时候mTv.getLineCount()
才会一直获取不到值,不光这样,宽高也会获取不到。所以我们需要一个方法来知道view是否绘制结束!
这里我是通过addOnGlobalLayoutListener
来实现,首先添加一个Java接口类:
ViewOnGlobalLayoutInterface:
public interface ViewOnGlobalLayoutInterface {
void onViewFinished(boolean isExpand); //true:展开状态 false:收缩状态
}
接着在ExpandableTextView
类里面添加一个方法:
public void addViewGlobalListener(final ViewOnGlobalLayoutInterface onGlobalLayoutInterface) {
ViewTreeObserver vto = mTv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mTv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (onGlobalLayoutInterface != null) {
onGlobalLayoutInterface.onViewFinished(isShowExpand());
}
}
});
}
private boolean isShowExpand() {
return mTvLineCount > mMaxCollapsedLines;
}
好了最后在代码里面调用:
expandableTextView.addViewGlobalListener(new ViewOnGlobalLayoutInterface() {
@Override
public void onViewFinished(boolean isExpand) {
if (isExpand) {
expand_collapse_rl.setVisibility(View.VISIBLE);
} else {
expand_collapse_rl.setVisibility(View.GONE);
}
}
});
这里顺便说一句,我们添加的“展开全部”文本如果需要点击事件的话也是需要自己写的,如下:
expand_collapse_tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandableTextView.onClick(v);
}
});
设置完点击事件监听后,直接调用ExpandableTextView里面的onClick()方法就好了。
问题三
当你遇到需要刷新页面,也就是文本行数变化了需要重新调用ExpandableTextView的setText("")
方法的时候,你会发现我们之前的布局显隐方法判断会导致布局发生变化;这里楼主的场景是:当前界面点击ExpandableTextView为展开状态,进入下一个页面然后返回,这个时候改变ExpandableTextView的文本行数刷新,会发现底部会多出一行空白或者文本由短变长的时候布局不显示了。为什么出现这个原因还得看源码,主要是onMeasure
方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If no change, measure and return
if (!mRelayout || getVisibility() == View.GONE) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
mRelayout = false;
// Setup with optimistic case
// i.e. Everything fits. No button needed
mButton.setVisibility(View.GONE);
mTv.setMaxLines(Integer.MAX_VALUE);
// Measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If the text fits in collapsed mode, we are done.
mTvLineCount = mTv.getLineCount();
if (mTvLineCount <= mMaxCollapsedLines) {
return;
}
// Saves the text height w/ max lines
mTextHeightWithMaxLines = getRealTextViewHeight(mTv);
// Doesn't fit in collapsed mode. Collapse text view as needed. Show
// button.
if (mCollapsed) {
mTv.setMaxLines(mMaxCollapsedLines);
}
mButton.setVisibility(View.VISIBLE);
// Re-measure with new setup
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTv.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
}
});
// Saves the collapsed height of this ViewGroup
mCollapsedHeight = getMeasuredHeight();
}
}
分析:当布局为展开状态的时候mCollapsed 的值为false,也就是说再次刷新页面的时候上诉代码的最后一处:
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTv.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
}
});
// Saves the collapsed height of this ViewGroup
mCollapsedHeight = getMeasuredHeight();
}
这段代码是没有执行的!!!也就是说这个时候的mCollapsedHeight 和 mMarginBetweenTxtAndBottom 的值还是之前展开状态的值,所以会导致多出一段空白,既然知道了,那只要每次刷新页面的时候将mCollapsed
重置下就好了,对应的箭头图标也得重置,后续给出代码。
解决了上诉问题,那当文本由短变长的时候隐藏布局代码失效的问题呢?这里还是得看上面的onMeasure
方法;之前我们通过添加addViewGlobalListener
方法解决了布局的显隐问题,但是那个方法是在界面绘制完毕后才会调用的,然而现在我们只是重新刷新了下页面,所以当然不会调用,这里只能通过添加一个接口来监听改变了,这里我直接在ExpandableTextView
的 OnExpandStateChangeListener
接口里添加了一个方法void onViewStateChanged(boolean isExpanded, boolean isShowExpanded)
:
public interface OnExpandStateChangeListener {
/**
* Called when the expand/collapse animation has been finished
*
* @param textView - TextView being expanded/collapsed
* @param isExpanded - true if the TextView has been expanded
*/
void onExpandStateChanged(TextView textView, boolean isExpanded);
/**
* Called when the layout has changed
*
* @param isExpanded - true if the TextView has been expanded
* @param isShowExpanded - true if the layout has been show
*/
void onViewStateChanged(boolean isExpanded, boolean isShowExpanded);
}
然后在onMeasure
里面调用:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If no change, measure and return
if (!mRelayout || getVisibility() == View.GONE) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
mRelayout = false;
// Setup with optimistic case
// i.e. Everything fits. No button needed
mButton.setVisibility(View.GONE);
mTv.setMaxLines(Integer.MAX_VALUE);
// Measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If the text fits in collapsed mode, we are done.
mTvLineCount = mTv.getLineCount();
if (mListener != null) {
mListener.onViewStateChanged(!mCollapsed, isShowExpand());
}
if (mTvLineCount <= mMaxCollapsedLines) {
return;
}
// Saves the text height w/ max lines
mTextHeightWithMaxLines = getRealTextViewHeight(mTv);
// Doesn't fit in collapsed mode. Collapse text view as needed. Show
// button.
if (mCollapsed) {
mTv.setMaxLines(mMaxCollapsedLines);
}
mButton.setVisibility(View.VISIBLE);
// Re-measure with new setup
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTv.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
}
});
// Saves the collapsed height of this ViewGroup
mCollapsedHeight = getMeasuredHeight();
}
}
这里isShowExpanded 和 isExpanded 两个参数,一个是判断布局的显隐,一个是判断文本“展开/收缩”的状态。
最后在代码里面调用:
@Bind(R.id.expand_collapse_ll)
RelativeLayout expandCollapseLL;
@Bind(R.id.tv_personal_instruction)
ExpandableTextView expandableTextView;
@Bind(R.id.expand_collapse_tv)
TextView expand_collapse_tv;
expand_collapse_tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandableTextView.onClick(v);
}
});
expandableTextView.setOnExpandStateChangeListener(new ExpandableTextView.OnExpandStateChangeListener() {
@Override
public void onExpandStateChanged(TextView textView, boolean isExpanded) {
if (isExpanded) {
expand_collapse_tv.setText("收起");
} else {
expand_collapse_tv.setText("展开全部");
}
}
//页面刷新需要,textview发生变化,对应布局的改变
@Override
public void onViewStateChanged(boolean isExpanded, boolean isShowExpanded) {
if (isShowExpanded) {
if (expandCollapseLL.getVisibility() != View.VISIBLE) expandCollapseLL.setVisibility(View.VISIBLE);
if (isExpanded) {
expand_collapse_tv.setText("收起");
} else {
expand_collapse_tv.setText("展开全部");
}
} else {
if (expandCollapseLL.getVisibility() != View.GONE) expandCollapseLL.setVisibility(View.GONE);
}
}
});
其实有了onViewStateChanged
这个回调,问题二也可以解决了,也就不用调用addViewGlobalListener
这个方法了。
好了以上就是楼主在使用 ExpandableTextView 的时候遇到的问题以及解决方法,下面给出完整代码:
http://download.csdn.net/detail/mackkill/9885405