ExpandableTextView的源码解读

这个自定义TextView可以实现内部文字的折叠和扩展显示,效果如下:

preview

有兴趣的同学可以自行 点击下载源码,里面的注释已经写的很完善了。这里我将我的代码整理分享出来:
首先需要定义ExpandableTextView的属性资源文件 attrs.xml



    
        
        
        
        
        
        
        
        
    

各属性的意义通过名称大家应该也都能理解出来,论学好英语的重要性啊......扯远了,接下来需要给ExpandableTextView指定布局文件layout_textview_expand_animation.xml代码如下:




    

    

        

        

        
    

好接下来就是我们的主题了,ExpandableTextView类的代码编写了。代码如下,我已经将需要注释的地方都进行了注释。

public class TextViewExpandable extends LinearLayout implements View.OnClickListener{
    /**
     * TextView
     */
    private TextView textView;

    /**
     * 收起/全部TextView
     * 
shrink/expand TextView */ private TextView tvState; /** * 点击进行折叠/展开的图片 *
shrink/expand icon */ private ImageView ivExpandOrShrink; /** * 底部是否折叠/收起的父类布局 *
shrink/expand layout parent */ private RelativeLayout rlToggleLayout; /** * 提示折叠的图片资源 *
shrink drawable */ private Drawable drawableShrink; /** * 提示显示全部的图片资源 *
expand drawable */ private Drawable drawableExpand; /** * 全部/收起文本的字体颜色 *
color of shrink/expand text */ private int textViewStateColor; /** * 展开提示文本 *
expand text */ private String textExpand; /** * 收缩提示文本 *
shrink text */ private String textShrink; /** * 是否折叠显示的标示 *
flag of shrink/expand */ private boolean isShrink = false; /** * 是否需要折叠的标示 *
flag of expand needed */ private boolean isExpandNeeded = false; /** * 是否初始化TextView *
flag of TextView Initialization */ private boolean isInitTextView = true; /** * 折叠显示的行数 *
number of lines to expand */ private int expandLines; /** * 文本的行数 *
Original number of lines */ private int textLines; /** * 显示的文本 *
content text */ private CharSequence textContent; /** * 显示的文本颜色 *
content color */ private int textContentColor; /** * 显示的文本字体大小 *
content text size */ private float textContentSize; /** * 动画线程 *
thread */ private Thread thread; /** * 动画过度间隔 *
animation interval */ private int sleepTime = 22; /** * handler信号 *
handler signal */ private final int WHAT = 2; /** * 动画结束信号 *
animation end signal of handler */ private final int WHAT_ANIMATION_END = 3; /** * 动画结束,只是改变图标,并不隐藏 *
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); // 设置右下角显示状态的文字颜色 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); // 设置默认值 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) { // 得到系统的布局解析器 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); } /** * 设置显示文本的TextView和显示底部标志的Layout设置监听 */ 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) {// 如果是折叠状态进行非折叠处理 doAnimation(expandLines, textLines, WHAT_EXPAND_ONLY); } else { doAnimation(textLines, expandLines, WHAT_EXPAND_ONLY); } isShrink = !isShrink; } /** * 对外设置文本的方法 * @param charSequence:是String的父接口,就是字符序列 */ public void setText(CharSequence charSequence) { textContent = charSequence; // 设置显示的TextView显示文本 textView.setText(charSequence); // A view tree observer is used to register listeners that can be notified of global changes in the view tree. // 这是一个注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。 // 这里指的全局 事件包括而且不局限在以下几个:整个视图树的布局变化,开始绘制视图,触摸模式改变等等。 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代表继续当前绘制,false代表取消 // 判断该View是否应该初始化 if (!isInitTextView) { return true; } // 未初始化进行初始化 isInitTextView = false; // 得到当前文本的总行数 textLines = textView.getLineCount(); // 设置是否需要显示扩展(总行数与当前显示的行数作比较) isExpandNeeded = textLines > expandLines; if (isExpandNeeded) { // 是否启用折叠标志 isShrink = true; // 调用动画 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: // 不断修改当前TextView的最大行数 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 开始动画的起点行数
start index of animation * @param endLines 结束动画的终点行数
end index of animation * @param what 动画结束后的handler信号标示
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); // 休眠一定时刻 try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendMessage(msg); } } else if (startLines < endLines) { // 如果起始行小于终止行向下扩展到终止行 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); } } // 动画结束后发送结束的信号 // animation end,send signal Message msg = handler.obtainMessage(what, endLines, 0); handler.sendMessage(msg); } }).start(); } /** * 负责改变折叠或展开状态的方法 * @param endLines */ private void changeExpandState(int endLines) { rlToggleLayout.setVisibility(VISIBLE); if (endLines > expandLines) {// 显示展开的状态 ivExpandOrShrink.setBackground(drawableShrink); tvState.setText(textShrink); } else {// 显示折叠的状态 ivExpandOrShrink.setBackground(drawableExpand); tvState.setText(textExpand); } } /** * 设置折叠或展开的状态方法 * @param endLines */ private void setExpandState(int endLines) { if (endLines < textLines) {// 小于总行数,设置为可以折叠或扩展状态 isShrink = true; rlToggleLayout.setVisibility(VISIBLE); ivExpandOrShrink.setBackground(drawableExpand); textView.setOnClickListener(this); tvState.setText(textExpand); } else {// 设置为不显示折叠状态 Log.e("xns", "not show shrink"); isShrink = false; rlToggleLayout.setVisibility(GONE); ivExpandOrShrink.setBackground(drawableShrink); textView.setOnClickListener(null); tvState.setText(textShrink); } } /** * 无需折叠 * 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; } /** * 取得显示的文本内容 * get content text * * @return content text */ public CharSequence getTextContent() { return textContent; } public int getSleepTime() { return sleepTime; } public void setSleepTime(int sleepTime) { this.sleepTime = sleepTime; } }
  • 整体设计的精髓即是通过handle接受Message后,不断的修改内部TextView的最大行数,然后调用该TextView的初始化方法,来完成动画的显示。
  • 这里还涉及到一个新的知识点即ViewTreeObserver类,简单来说它是一个监听ViewTree的观察者,在视图树中全局事件改变时得到通知。全局事件包括但不局限于如:整个视图树的布局变化,开始绘制视图,触摸模式改变等。
    ViewTreeObserver是不能被应用程序实例化的,因为它是由视图提供的,通过view.getViewTreeObserver()获取。
    网上有很多关于这个类的介绍,这里我推荐一篇博文,你可以大致了解这个类的一些基本知识。

其余都是一些逻辑上的代码,通过注释我相信大家都可以读懂它的实现方法。朝阳在前方,同志们继续前进吧。

你可能感兴趣的:(ExpandableTextView的源码解读)