Android:ExpandableTextView 的扩展

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的绘制需要经过onMeasureonLayoutonDraw,这之间需要时间,所以这个时候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 方法解决了布局的显隐问题,但是那个方法是在界面绘制完毕后才会调用的,然而现在我们只是重新刷新了下页面,所以当然不会调用,这里只能通过添加一个接口来监听改变了,这里我直接在ExpandableTextViewOnExpandStateChangeListener 接口里添加了一个方法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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值