可收缩的TextView

来源:互联网 发布:下载doov软件乐园 编辑:程序博客网 时间:2024/04/28 13:20

可收缩的TextView

1.上来不啰嗦效果图如下:

这里写图片描述


如上图所示,我们项目中有时候需求这种效果。那么今天我们一起来实现这样一个东西把。

2.我们来分析如何实现:

  • 如图我们可以看出是一个容器包裹着两个控件,文字TextView控件和另外一个随便的可点击控件,在点击之后我们可以看出有动画效果,而且有默认的初始行数和高度,以及展开的高度和行数等。所以接下来我们来自定义自己的容器。

3.确定容器布局所需变量

  • 首先我们分析我们这个容器布局所需要的变量:
  • 1:显示内容的控件。TextView visible_context_tv;
  • 2:可点击控件。TextView button_expand_tv;
  • 3 :展开后允许显示的最大行数:int maxExpandLines;
  • 4 :动画的时间:int animal_duration;
  • 5 :来个标记记录文字是否发生了变动:flag_isChang=false;
  • 6 :没有展开时候的TextView的高度:shrinkageHeight=0;
  • 7 :判断是否在执行动画:boolean isAnimate=false;
  • 8:我们需要定义一个接口来监听我们的容器布局展开收缩的状态:
    ExpandStateListener expandStateListener;

4.开始撸代码了
首先来定义一个类CanExpandTextView继承LinearLayout或者其他容器布局,我们由于TextView内容控件和点击控件都是上下排列,我们就继承LinearLayout代码如下:

import android.content.Context;import android.content.res.TypedArray;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.widget.LinearLayout;import android.widget.TextView;/** * Created by Administrator on 2017/9/5/005. */public class CanExpandTextView extends LinearLayout {    //- 1:显示内容的控件。TextView visible_context_tv;    private TextView visible_context_tv;    //- 2:可点击控件。TextView button_expand_tv;    private TextView button_expand_tv;    // - 3 :展开后允许显示的最大行数:    int maxExpandLines;    //- 4 :动画的时间:    int animal_duration;    //- 5 :来个标记记录文字是否发生了变动:    boolean flag_isChang = false;    //- 6 :没有展开时候的TextView的高度:    int shrinkageHeight = 0;    //- 7 :判断是否在执行动画:    boolean isAnimate = false;    //- 8:我们需要定义一个接口来监听我们的容器布局展开收缩的状态:    ExpandStateListener expandStateListener;    public CanExpandTextView(Context context) {        super(context);    }    public CanExpandTextView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        //初始化我们所需要的属性        init(context,attrs);    }    /***     * 这里我们来实现自定义的属性     * @param context     * @param attrs     */    private void init(Context context, AttributeSet attrs) {        //首先设置容器布局的方向:竖直的        setOrientation(VERTICAL);        /**这里我们需要自定义属性:这里我我们都知道,刚接触android的        可以去自己去看看自定义属性:**/        TypedArray typedArray=context.obtainStyledAttributes        (attrs,R.styleable.ExpandParames);        /**初始TextView显示字体行数:这里注意了默认的最大展开能允许显示          的text行数可以这里设置也可以咋布局中设置的**/        //我就在布局中设置,相当高大上        maxExpandLines=typedArray.getInteger(R.styleable.        ExpandParames_max_expend_lines,0);        /**这里设置动画执行的时间长度,同样可在布局xml中折花枝也可以在这里设置。        我就在xml中设置了**/        animal_duration=typedArray.getInteger(R.styleable.\        ExpandParames_animal_duration,0);        //最后回收这个TypedArray:        typedArray.recycle();    }    private interface ExpandStateListener {        void ExpandStarteChangerListener(boolean isExpand);    }}

上面我们在初始化构造方法中初始化了容器布局需要的两个属性, 接下来我们需要给容器布局添加2个控件,一个是内容展示TextView,另一个是点控件我们这里有设置成TextView吧。当然只要是可点击控件你随便都可以。这时候我们需要知道onFinishInflate方法,我们特意点击查看源码在它的父类的父类View里面发现如下:

      /**     * Finalize inflating a view from XML.  This is called as the last phase     * of inflation, after all child views have been added.     *     * <p>Even if the subclass overrides onFinishInflate, they should always be     * sure to call the super method, so that we get called.     */    @CallSuper    protected void onFinishInflate() {    }

我们可以知道但xml初始化完成之后着个方法用了填充所有的子视图,所以我们载这个方法里面进行子视图控件的设置添加,在添加子view之前我们需要自定义两个控件TextView。并初始化。我们在res/values下面来定义一个ids.xml代码如下:

<?xml version="1.0" encoding="utf-8"?><resources>    <item name="visible_context_tv" type="id"/>    <item name="click_expand_tv" type="id"/></resources>

接下来初始化这个两个控件:

@Override    protected void onFinishInflate() {        super.onFinishInflate();        //初始化容器内部的两个控件        visible_context_tv= (TextView) findViewById(R.id.visible_context_tv);        click_expand_tv= (TextView) findViewById(R.id.click_expand_tv);        //给这个可点击控件设置点击时间。        click_expand_tv.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {            }        });    }

我们点击这个控件时候需要执行动画这个容器布局随着动画展开,如果展开那么随着动画收缩,这里我们需要自定义一个属性动画:

public class ExpandaAnimal extends Animation {        int startHeight, endHeight;        public ExpandaAnimal(int startHeight, int endHeight) {            //这是执行的时间            setDuration(animal_duration);            this.startHeight = startHeight;            this.endHeight = endHeight;        }        /***         *         * @param interpolatedTime:这个值用来设置时间的0-1变化范围         * @param t 设置动画效果和状态         */        @Override        protected void applyTransformation(float interpolatedTime, Transformation t) {            super.applyTransformation(interpolatedTime, t);            //这里我们需要计算内容TextView的变化的动态高度height            //我们将开始的高度+内容变化高度            //内容变化高度=(endHeight-startHeight)*interpolatedTime            int height= (int) (startHeight+(endHeight-startHeight)*interpolatedTime);            //动态的设置内容TextView的高度            visible_context_tv.setHeight(height-lastHeight);            //从新摆放容器布局的子view            CanExpandTextView.this.getLayoutParams().height=height;            CanExpandTextView.this.requestLayout();           }    }

接下来我们来设置动画,当点击展开时候来向下展开,当点击收起向上收缩。如果内容TextView是收缩的那么点击控件显示展开二字,否收起。代码如下:

 @Override    protected void onFinishInflate() {        super.onFinishInflate();        //初始化容器内部的两个控件        visible_context_tv = (TextView) findViewById(R.id.visible_context_tv);        click_expand_tv = (TextView) findViewById(R.id.click_expand_tv);        //给这个可点击控件设置点击时间。        click_expand_tv.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                ExpandaAnimal expandaAnimal;                isClosed = !isClosed;                if (isClosed) {                    //设置显示让用户去操作                    click_expand_tv.setText("..展开");                    if (expandStateListener != null) {                        expandStateListener.ExpandStarteChangerListener(true);                    }                    //收缩所以我们开始高度getHeight(),结束变为0                  expandaAnimal=new ExpandaAnimal(getHeight(),shrinkageHeight);                } else {                   click_expand_tv.setText("收起");                    if(expandStateListener!=null){                        expandStateListener.ExpandStarteChangerListener(false);                    }                    //展开:结束执行过程时候高度为内容控件的高度+点击控件的高度                    expandaAnimal=new ExpandaAnimal(getHeight(),shrinkageHeight+realHeight);                }                //让执行之后的动画保存当前状态。                expandaAnimal.setFillAfter(true);                expandaAnimal.setAnimationListener(new Animation.AnimationListener() {                    @Override                    public void onAnimationStart(Animation animation) {                        isAnimate=true;                    }                    @Override                    public void onAnimationEnd(Animation animation) {                        clearAnimation();                        isAnimate=false;                    }                    @Override                    public void onAnimationRepeat(Animation animation) {                    }                });                clearAnimation();                startAnimation(expandaAnimal);            }        });    }

最后一步:我们需要在onMeasure里面进行测量:

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //1.如果控件被设置Visible=gon或者内容控件TextView内容没有变化那么没必要测量        if (getVisibility() == GONE || flag_isChang == false) {            return;        }        flag_isChang = false;        //初始化默认状态,显示文本就可以        click_expand_tv.setVisibility(GONE);        visible_context_tv.setMaxLines(Integer.MAX_VALUE);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //如果本身没有文字行数没有达到限制最少的行数那么就没必要展开或者        if (visible_context_tv.getLineCount() <= maxExpandLines) {            return;        }        //获取内容TexView的真实高度,后面我们需要用到        realHeight = getRealHeightTextView(visible_context_tv);        //如果处于收缩状态,则设置最多显示行数        if (isClosed) {            visible_context_tv.setLines(maxExpandLines);        }        click_expand_tv.setVisibility(VISIBLE);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //如果是收缩的状态那么需要去        if (isClosed) {            visible_context_tv.post(new Runnable() {                @Override                public void run() {                    //剩余高度=当前收缩高度-内容控件的高度                    lastHeight = getHeight() - visible_context_tv.getHeight();                    //收缩时候的TextView的高度                    shrinkageHeight =visible_context_tv.getHeight();                }            });        }    }

当然我们也可以添加一个方法去初始化时候设置是展开还是关闭,我就在setText时候去设置:
代码如下:

  public void setText(String text) {        flag_isChang = true;        visible_context_tv.setText(text);    }    public void setText(String text, boolean isClosed) {        this.isClosed = isClosed;        if (isClosed) {            click_expand_tv.setText("..展开");        } else {            click_expand_tv.setText("\n收起");        }        clearAnimation();        setText(text);        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;    }

接下来我贴出xml代码:这里我们可以使用我们自定义属性:首先命名自定义空间:
xmlns:attrs=”http://schemas.android.com/apk/res-auto”然后就可以使用我们自定义的属性
attrs:max_expend_lines=”3”
attrs:animal_duration=”300”

<?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"    xmlns:attrs="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.administrator.textputtext.MainActivity">    <ScrollView        android:layout_width="match_parent"        android:layout_height="match_parent"       >        <com.example.administrator.textputtext.CanExpandTextView            android:id="@+id/expandable_text"            android:layout_width="368dp"            android:layout_height="wrap_content"            attrs:max_expend_lines="3"            attrs:animal_duration="300"          >            <TextView                android:id="@id/visible_context_tv"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />            <TextView                android:id="@id/click_expand_tv"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="..展开"                android:textColor="@android:color/holo_red_light" />        </com.example.administrator.textputtext.CanExpandTextView>    </ScrollView></LinearLayout>

我们在MainActivity里面调用就可以代码如下:

import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    private CanExpandTextView mTv_content;    private String[] strings=new String[]{};    private TextView tv;    private Button bt;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTv_content= (CanExpandTextView) findViewById(R.id.expandable_text);        strings=getResources().getStringArray(R.array.news);        StringBuffer buffer=new StringBuffer();        for (int i = 0; i <strings.length ; i++) {            buffer.append(strings[i]);        }        mTv_content.setText("\n\b\b\b\b\b我们可以看到这个代理HeaderViewListAdapter类,它处理完头布局尾布局和列表item直接的关系," +                "然后调用 return mAdapter.getView(adjPosition, convertView, parent);来和我们自定义的adapter交互。" +                "我们现在知道了listView的源码添加头和尾的原理:就是通过一个代理adapter来处理完头布局和尾布局,然后去和我" +                "们自定义的MyAdapter交互。接下来我们来分装自己的RecylerView实现listView一样添加头布局和尾布局的方法。。",true);    }}

接下来愉快的运行代码如图1所示:
最后贴出github下面地址自己需要就下载:
https://github.com/luhenchang/TextPutText.git

原创粉丝点击