图1 收缩状态 图2 展开状态
今天所讲述的是一个比较常见的一个功能模块,可伸缩的文本控件,可以用于显示商品的详情信息,但是为了节省空间,可以先让详情信息显示一部分,如果用户要看全部信息,只要点击展开,商品的详情信息就全部展示出来,如果用户看完之后,还可以点击收缩,把详情信息收缩起来,只展示一部分。其实这样的功能Android系统提供的TextView控件也是可以做到这一点的,TextView可以设置其显示的最大行数来收缩文本,但是它做不到的是将展开/收缩文本加到最后一行的后面,系统并没有提供此类的方法。下面我们就来自定义一个控件来实现此功能,本控件的实现原理是基于流式布局的,关于流式布局详解的博客:http://blog.csdn.net/cj_286/article/details/53082901
本控件的实现原理是遍历要显示的文本字符,计算每一个字符所占空间的宽度,不断的累加,如果超过一行显示的宽度时,就保存该行所显示的所有字符,再换行到下一行继续累计下一行显示的字符,以此类推,直到最后一行的最后一个字符。当保存了每一行所占的字符串后,遍历每行字符串的集合,对应每行创建一个TextView显示一行字符串,如果是收缩状态在后面加展开,并对其设置点击事件,点击后触发展开;如果是展开状态,在最后一行后面添加收缩,也对其添加点击事件,点击后触发收缩;在收缩或展开的最后一行添加收缩和展开TextView,因为其父容器是FlowLayout,会将收缩/展开view直接添加在其后面,如果显示不下,就会到下一行显示,不会出现显示在窗体外面的情况,完美的结合。
如何来计算每个字符所占宽度的大小呢,就是先创建一个TextView,将要计算宽度的字符设置到TextView上,然后得到它测量的宽度即可。
/** * 临时文本控件,用于计算每个字符所占的空间大小,主要是宽度 * @param size * @return */ private TextView getTextView(float size){ TextView textView = new TextView(mContext); textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); textView.setTextSize(size); return textView; }
/** * 计算每一个字符的宽度 * @param textView 初始化工作的文本控件,载体 * @param c 字符 * @return */ private float getCharWidth(TextView textView, char c) { textView.setText(String.valueOf(c)); textView.measure(0, 0); return textView.getMeasuredWidth(); }解决了计算字符宽度的问题,下面就是得到回去要显示的字符串,遍历所有字符,计算每一行所能容纳的字符数,保存在行集合中。下面是遍历循环中的计算每行字符数的一段代码:
//得到字符的显示宽度 float charWidth = getCharWidth(mTextView,(char)mString.charAt(i)); //如果该行的显示的所有字符小于该行宽度,就继续添加 if(width + charWidth <= widthScreen){ width += charWidth; if((char)mString.charAt(i) == ' '){//记住一行中最后一次出现的空格 isNullCharIndex = i; } //防止英文字符之间不是连续的 if(!isA_z((char)mString.charAt(i))){ isNullCharIndex = i; } if(isNullCharIndex + WORD_LENGTH < i ){//如果连续15个字符还是没有空格就直接截断 isNullCharIndex = i; } }else{//一行已显示不下 //如果下一个字符不是最后一个,且是英文字母 if(mString.length()-1 >= i + 1 && isA_z((char)mString.charAt(i))){ //就从记下的空格的位置继续循环, i = isNullCharIndex ; if(mString.length() != i){ //保存空格之前的字符串 strings.add(mString.substring(index, i + 1)); //记下下一行的开始位置 index = i + 1; //下一行的宽度从0开始 width = 0; //累加宽度 //width += charWidth; //行数加1 rows ++; } continue; } //如果不是最后一行 if(mString.length() != i){ //记下该行的字符串 strings.add(mString.substring(index, i)); //记下下一行的开始位置 index = i; //下一行的宽度从0开始 width = 0; //累加宽度 width += charWidth; //行数加1 rows ++; } } }在计算好每行要显示的字符数组成的字符串后,遍历每行的字符串,创建显示该字符串的文本控件TextView,设置相应的属性参数,然后将其添加到流式布局FlowLayout中去,流式布局会自动的去排版每行显示文本的控件。在收缩/展开的最后一行在创建TextView显示收缩/展开文本,并对其设置点击事件,点击收缩就出发收缩,点击展开就触发展开。布局每行字符串时,对应着三种情况,第一种情况为:要显示的字符太少,不够对其施以展开触发,第二种情况:展开状态,第三种情况:收缩状态,代码如下:
//第一种情况:在收缩文本行数以内 (展开和收缩都是一样的,所以就没有收缩和展开) if(rows <= mZoomRows && !isExpand && mString.length()-1 == i){ strings.add(mString.substring(index, i+1));//加最后一行 for(int j=0;j<strings.size();j++){ TextView textView = new TextView(mContext); textView.setTextSize(mCharSize); textView.setTextColor(mDescColor); textView.setText(strings.get(j)); //textView.setBackgroundColor(Color.RED); mFlowLayout.addView(textView); } break; } //第二种情况:展开时,已经到最后一个字符 if(isExpand && mString.length()-1 == i){ strings.add(mString.substring(index, i+1));//加最后一行 for(int j=0;j<strings.size();j++){ TextView textView = new TextView(mContext); textView.setTextSize(mCharSize); textView.setText(strings.get(j)); textView.setTextColor(mDescColor); //textView.setBackgroundColor(Color.GREEN); mFlowLayout.addView(textView); } mTextViewExpand.setText(" "+mExpandTextClose); setExpandTextDrawable(mCloseResId); mTextViewExpand.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //Toast.makeText(mContext, "收缩", 0).show(); mFlowLayout.removeAllViews(); expandText(false); } }); mFlowLayout.addView(mTextViewExpand); break; } //第三种情况:收缩时,但是文本已经超出了收缩的个数,所以只保留收缩的文本字数 if(rows == mZoomRows && index + mZoomChar == i && !isExpand){ strings.add(mString.substring(index, i+1));//加最后一行 strings.add("..."); for(int j=0;j<strings.size();j++){ TextView textView = new TextView(mContext); textView.setTextSize(mCharSize); textView.setText(strings.get(j)); textView.setTextColor(mDescColor); //textView.setBackgroundColor(Color.BLUE); mFlowLayout.addView(textView); } mTextViewExpand.setText(" "+mExpandTextOpen); setExpandTextDrawable(mOpenResId); mTextViewExpand.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //Toast.makeText(mContext, "展开", 0).show(); mFlowLayout.removeAllViews(); expandText(true); } }); mFlowLayout.addView(mTextViewExpand); break; }到这里核心的代码就讲完了,下面就是给该控件自定义一些属性,让调用者在xml布局中就可以设置相应的数据,代码中也会提供相应的方法改变一些属性等。attrs.xml中定义相应的属性:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ExpandTextView"> <attr name="descZoomRows" format="integer" /><!-- 完全显示的行数 实际为descZoomRows + 1行 --> <attr name="descText" format="string" /><!-- 显示文本 --> <attr name="descSize" format="float" /><!-- 显示文本的字体大小 --> <attr name="descColor" format="reference|color" /><!-- 显示文本的颜色 --> <attr name="expandTextOpen" format="string" /><!-- 设置展开文本 --> <attr name="expandTextClose" format="string" /><!-- 设置收缩文本 --> <attr name="expandTextOpenDrawable" format="reference" /><!-- 设置展开文本右边的图标 --> <attr name="expandTextCloseDrawable" format="reference" /><!-- 设置收缩文本右边的图标 --> <attr name="expandTextColor" format="reference|color" /><!-- 展开/收缩的文本颜色 --> <attr name="expandTextSize" format="float" /><!-- 展开/收缩的文本大小 --> </declare-styleable> </resources>在该控件中获取相应的属性值
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ExpandTextView); mZoomRows = a.getInt(R.styleable.ExpandTextView_descZoomRows, 2); mString = a.getString(R.styleable.ExpandTextView_descText); if(TextUtils.isEmpty(mString)){ mString = ""; } mDescSize = a.getFloat(R.styleable.ExpandTextView_descSize, 14); mCharSize = mDescSize; mDescColor = a.getColor(R.styleable.ExpandTextView_descColor, 0); mExpandTextOpen = a.getString(R.styleable.ExpandTextView_expandTextOpen); mExpandTextClose = a.getString(R.styleable.ExpandTextView_expandTextClose); mExpandTextColor = a.getColor(R.styleable.ExpandTextView_expandTextColor, 0); mExpandTextSize = a.getFloat(R.styleable.ExpandTextView_expandTextSize, 14); mOpenResId = a.getResourceId(R.styleable.ExpandTextView_expandTextOpenDrawable, 0); mCloseResId = a.getResourceId(R.styleable.ExpandTextView_expandTextCloseDrawable, 0);可伸缩的文本控件ExpandTextView到这里就讲解结束了,欢迎大家留言
源码下载地址:CSDN
GitHub