欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android UI实现多行文本折叠展开效果

程序员文章站 2024-03-04 18:59:06
上文介绍了,通过edittext实现textview单行长文本水平滑动效果。 本文继续介绍了多行文本折叠展开,自定义布局view实现多行文本折叠和展开 1.概述   ...

上文介绍了,通过edittext实现textview单行长文本水平滑动效果。

本文继续介绍了多行文本折叠展开,自定义布局view实现多行文本折叠和展开

1.概述

  经常在app中能看到有引用文章或大段博文的内容,他们的展示样式也有点儿意思,默认是折叠的,当你点击文章之后它会自动展开。再次点击他又会缩回去。
  网上有找到部分效果,感觉不是很满意。最后自己尝试用 自定义布局layout 写了个demo。比较简陋,不过可以用了。有这方面需求的朋友可以稍加改造下。如有更好的创意,也不妨分享一下。   

效果图:

Android UI实现多行文本折叠展开效果

2.具体实现

但从实现效果方面来看,只用简单定义必要view即可,后变为了方便扩展使用和挪用,又对整个布局进行封装,方便直接使用。

2.1 通过多个布局组合实现

第一想法当然是用多个view组合来实现。那么久定义一个linearlayout布局分别嵌套textview和imageview来做。

大概步骤:

- 定义布局,垂直的线性linearlayout布局、textview和imageview。 在layout中定义基本组件。
- 设置textview的高度为指定行数*行高。 不使用maxline的原因是maxline会控制显示文本的行数,不方便后边使用动画展开全部内容。因此这里textview的高度也因该为wrap_content。
- 给整个布局添加点击事件,绑定动画。 点击时,若textview未展开则展开至其实际高度,imageview 旋转;否则回缩至 指定行数*行高 , imageview 旋转缩回。   

开始编写代码:

1.在xml中定义布局:

 <linearlayout
 android:id="@+id/description_layout"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="vertical"
 android:paddingleft="12dip"
 android:paddingright="12dip"
 android:paddingtop="5dip" >

 <textview
 android:id="@+id/description_view"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:textcolor="@android:color/black"
 android:textsize="18dip" >
 </textview>

 <imageview
 android:id="@+id/expand_view"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_gravity="right"
 android:paddingbottom="5dip"
 android:paddingleft="5dip"
 android:paddingright="5dip"
 android:paddingtop="5dip"
 android:src="@drawable/text_ic_expand"
 android:visibility="gone" />
 </linearlayout>

2.首先在activity中定义并初始化这些view:

public class mainactivity extends activity {
 textview descriptionview;
 view layoutview ,expandview; //linearlayout布局和imageview
 int maxdescripline = 3; //textview默认最大展示行数

 //在oncreate中初始化
 {
 layoutview = findviewbyid(r.id.description_layout);
 descriptionview = (textview)findviewbyid(r.id.description_view);
 expandview = findviewbyid(r.id.expand_view);
 }
}

3.然后设置textview显示文本,再根据默认展示行数设置其高度,并根据其是否已完全显示(当前展示行数是否大于等于实际行数)来判断需不需要点击更多按钮。

 //设置文本
 descriptionview.settext(gettext(r.string.content));

 //descriptionview设置默认显示高度
 descriptionview.setheight(descriptionview.getlineheight() * maxdescripline);
 //根据高度来判断是否需要再点击展开
 descriptionview.post(new runnable() {

 @override
 public void run() {
 expandview.setvisibility(descriptionview.getlinecount() > maxdescripline ? view.visible : view.gone);
 }
 }); 

因为textview设置的是wrap_content,所以会显示实际高度和行数,这里根据maxdescripline来设置其高度。
  看了最后一行代码可能有人会问?imageview (点击展开更多)是否应该显示 的判断逻辑为什么要放在post方法里边呢? 这是由于在oncreate方法中定义设置的textview不会马上渲染并显示,所以textview的getlinecount()获取到的值一般都为零,因此使用post会在其绘制完成后来对imageview进行显示控制。
  ps: 感觉我描述不清的朋友可以看下* 上的讲解 how to use getlinecount() in textview android.

4.给layoutview设置点击事件。

给imageview定义一个rotateanimation的旋转动画,在旋转过程中根据旋转百分比进度控制textview高度,进而达到我们想要的效果。

 layoutview.setonclicklistener(new view.onclicklistener() {
 boolean isexpand;//是否已展开的状态

 @override
 public void onclick(view v) {
 isexpand = !isexpand;
 descriptionview.clearanimation();//清楚动画效果
 final int deltavalue;//默认高度,即前边由maxline确定的高度
 final int startvalue = descriptionview.getheight();//起始高度
 int durationmillis = 350;//动画持续时间
 if (isexpand) {
  /**
  * 折叠动画
  * 从实际高度缩回起始高度
  */
  deltavalue = descriptionview.getlineheight() * descriptionview.getlinecount() - startvalue;
  rotateanimation animation = new rotateanimation(0, 180, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
  animation.setduration(durationmillis);
  animation.setfillafter(true);
  expandview.startanimation(animation);
 } else {
  /**
  * 展开动画
  * 从起始高度增长至实际高度
  */
  deltavalue = descriptionview.getlineheight() * maxdescripline - startvalue;
  rotateanimation animation = new rotateanimation(180, 0, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
  animation.setduration(durationmillis);
  animation.setfillafter(true);
  expandview.startanimation(animation);
 }
 animation animation = new animation() {
  protected void applytransformation(float interpolatedtime, transformation t) { //根据imageview旋转动画的百分比来显示textview高度,达到动画效果
  descriptionview.setheight((int) (startvalue + deltavalue * interpolatedtime));
  }
 };
 animation.setduration(durationmillis);
 descriptionview.startanimation(animation);
 }
 });

至此,通过布局已经实现了我们想要的效果。具体代码参见代码示例 的第一部分。
  当然,我们可以这样使用,但是每次都这么重写未免显得有些麻烦。因此有必要把他写成一个单独控件,方便我们以后的开袋即食。废话不多说,上菜。

2.2 通过自定义view组合封装

这个view的布局结构并不打算使用xml来定义layout,直接定义一个继承linearlayout的moretextview类.这个类里边添加textview和imageview。

1.使用styleable自定义view属性

为了后边能够方便的在xml布局中使用moretextview这个自定义view,类似通过

android:text = “xxx”
android:textsize = “xxx”

这样快捷的绑定文本内容和设置字体大小等属性,我们可以通过 declare-styleable在values文件下的xml中自定义我们想要的属性,并在view中获取和使用。详细使用declare-styleable的内容会在后边补充,这里简要说下。
  比如,moretextview应该有的基本属性,像 文本字体大小(textsize)、颜色(textcolor)和文本内容(text),还有默认显示行数(maxline)等几种属性。我们要想像textview一样直接在xml中设置绑定,可以这样做。
  首先在values目录下新建个attrs.xml(名字随意),并定义moretextview这些属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="moretextstyle">
 <attr name="textsize" format="dimension"/>
 <attr name="textcolor" format="color"/>
 <attr name="maxline" format="integer" />
 <attr name="text" format="string" />
 </declare-styleable>
</resources>

2.自定义moretextview并获取这些属性的值

  上边定义了这些属性,就等于允许我们在xml中使用

more:text = “xxx”
more:textsize = “xxx”
more : textcolor = “xxx”
more : maxline = “xxx”
(注: more这个关键字可以随意)

这样直接设置属性值。那么具体怎么取值,我们稍后来讲。

(1)定义moretextview的属性:

public class moretextview extends linearlayout{
 protected textview contentview; //文本正文
 protected imageview expandview; //展开按钮

 //对应styleable中的属性
 protected int textcolor; 
 protected float textsize;
 protected int maxline;
 protected string text;

 //默认属性值
 public int defaulttextcolor = color.black;
 public int defaulttextsize = 12;
 public int defaultline = 3;

 //....实现部分略
}

(2)moretextview的构造方法:

 public moretextview(context context, attributeset attrs) {
 super(context, attrs);
 initalize(); 
 initwithattrs(context, attrs); 
 bindlistener();
 }

这三个方法简单说明下:

initalize()初始化并添加view。初始化textview和imageview,并添加到moretextview中去。
initwithattrs(context, attrs)取值并设置。利用attrs从xml布局中取我们配置好的text/textsize/textcolor/maxline等属性的属性值,并设置到view上去。
bindlistener()绑定点击事件并设置动画。 给当前moretextview设置点击事件,实现点击折叠和展开。

各个方法的具体实现:

//初始化并添加view
protected void initalize() {
 setorientation(vertical); //设置垂直布局
 setgravity(gravity.right); //右对齐
 //初始化textview并添加
 contentview = new textview(getcontext());
 addview(contentview, linearlayout.layoutparams.match_parent, linearlayout.layoutparams.wrap_content);
 //初始化imageview并添加
 expandview = new imageview(getcontext());
 int padding = dip2px(getcontext(), 5);
 expandview.setpadding(padding, padding, padding, padding);
 expandview.setimageresource(r.drawable.text_ic_expand);
 linearlayout.layoutparams llp = new linearlayout.layoutparams(linearlayout.layoutparams.wrap_content, linearlayout.layoutparams.wrap_content);
 addview(expandview, llp);

取值并设置这部分有必要将一下。我们利用typedarray从定义的styleable中取出属性值,赋给我们定义好的类的属性变量。记得取完之后调用recycle()回收释放。

 protected void initwithattrs(context context, attributeset attrs) {
 typedarray a = context.obtainstyledattributes(attrs, 
 r.styleable.moretextstyle); 
 int textcolor = a.getcolor(r.styleable.moretextstyle_textcolor, 
 defaulttextcolor); //取颜色值,默认defaulttextcolor
 textsize = a.getdimensionpixelsize(r.styleable.moretextstyle_textsize, defaulttextsize);//取颜字体大小,默认defaulttextsize
 maxline = a.getint(r.styleable.moretextstyle_maxline, defaultline);//取颜显示行数,默认defaultline
 text = a.getstring(r.styleable.moretextstyle_text);//取文本内容

 //绑定到textview bindtextview(textcolor,textsize,maxline,text);

 a.recycle();//回收释放
 }

 //绑定到textview 
 protected void bindtextview(int color,float size,final int line,string text){
 contentview.settextcolor(color);
 contentview.settextsize(typedvalue.complex_unit_px,size);
 contentview.settext(text);
 contentview.setheight(contentview.getlineheight() * line);
 post(new runnable() {//前面已讲,不再赘述

 @override
 public void run() {
 expandview.setvisibility(contentview.getlinecount() > line ? view.visible : view.gone);

 }
 });
 }

最后设置点击事件。

 //点击展开与折叠,不再赘述
 protected void bindlistener(){
 setonclicklistener(new view.onclicklistener() {
 boolean isexpand;

 @override
 public void onclick(view v) {
 isexpand = !isexpand;
 contentview.clearanimation();
 final int deltavalue;
 final int startvalue = contentview.getheight();
 int durationmillis = 350;
 if (isexpand) {
  deltavalue = contentview.getlineheight() * contentview.getlinecount() - startvalue;
  rotateanimation animation = new rotateanimation(0, 180, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
  animation.setduration(durationmillis);
  animation.setfillafter(true);
  expandview.startanimation(animation);
 } else {
  deltavalue = contentview.getlineheight() * maxline - startvalue;
  rotateanimation animation = new rotateanimation(180, 0, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
  animation.setduration(durationmillis);
  animation.setfillafter(true);
  expandview.startanimation(animation);
 }
 animation animation = new animation() {
  protected void applytransformation(float interpolatedtime, transformation t) {
  contentview.setheight((int) (startvalue + deltavalue * interpolatedtime));
  }

 };
 animation.setduration(durationmillis);
 contentview.startanimation(animation);
 }
 });
 }

另外,定义几个方法方便外部调用(获取文本textview,直接设置文本内容),同时还定义了一个dip转像素的静态方法。

 public textview gettextview(){
 return contentview;
 }

 public void settext(charsequence charsequence){
 contentview.settext(charsequence);
 }

 public static int dip2px(context context, float dipvalue){ 
 final float scale = context.getresources().getdisplaymetrics().density;  
 return (int)(dipvalue * scale + 0.5f); 
 } 

其实到这里,我们的自定义多文本折叠展开moretextview已经完成了。如何使用呢?

在layout/xx.xml中使用

要想方便的使用我们刚刚的自定义属性来定义值,记得在xml namespace中定义应用:

自动引用命名空间res-auto
xmlns:more=”http://schemas.android.com/apk/res-auto”
或者 直接定义包名
xmlns:more=”http://schemas.android.com/apk/res/com.qiao.moretext”
命名空间后边跟的 more即下边你要使用自定义属性的开头部分。
比如我们的activity_main.xml

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:more="http://schemas.android.com/apk/res/com.qiao.moretext"
 android:id="@+id/root"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@android:color/white"
 android:orientation="vertical" >

 <com.qiao.moretext.moretextview
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_margin="5dip" 
 more:textcolor="@android:color/black"
 more:textsize="18dip"
 more:maxline="3"
 more:text="@string/content"/>

</linearlayout>

在java中直接定义使用
由于上边定义moretextview只定义了一种构造方法 moretextview(context context, attributeset attrs) ,所以使用时,也只能:

 moretextview content = new moretextview(mainactivity.this, null);
 content.settext(gettext(r.string.content));
 //然后addview到你要添加的地方

  当然,聪明如你,可肯定知道怎么定义另外的构造方法来简单实用啦。 
  –> 
  moretextview(context context){ 
    //使用默认值直接初始化 
    bindtextview(); 
  }

3.综述

综上呢,我们已经完成了所要实现的功能,作为ui呢,他可能会有些简陋,但作为一个demo起到示范作用已经够了。后边我们可能会考虑把它作为微博的一个listitem做成列表一样,并加入点赞等功能。有兴趣不妨做一下咯。。
  源码示例下载地址:http://xiazai.jb51.net/201610/yuanma/androidtouchmove(jb51.net).rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。