最近因为项目需求,需要开发类似Reddit里面无限循环的评论列表,于是就开始研究其实现方式和可用性,reddit评论如下:
从最开始的印象看,我们可以看到这是一个树状列表,如果没有限制的话,可以无限循环嵌套,当然我看过的第一实现想法是LinearLayout无限循环的套
这种方式最好实现,根据传输的数据结构,View一层层的add,就能实现;但是,由于嵌套过多,性能不行,故抛弃之。
一种方案不行,换一种方案,还是用LinearLayout实现,但是只有一层结构,不嵌套,通过数据结构来告知LinearLayout当前是第几层,根据不同层次来绘制布局,结果如图:
大致原理如下:
- 第一层绘制
- 第二层绘制
- 第三层绘制
- ...
根据层数的不同在最左边添加竖线,以上的实现是行的通的,就是数据结构转换需要花点时间。
按理来说,事情到这已经结束了,但是...但是...通过测试,当加载数据有几百条的时候,会非常非常卡,会卡几秒才加载出来,这非常影响应用体验,抛弃之。
一种方案又不行,继续研究,这次使用RecyclerView形式,至于为什么使用RecyclerView呢,因为它不管你有多少数据,只会加载可见页面的数据,故而对于数据比较多的情况下,效率和体验都能不错(当然ListView应该也行),首先构建数据结构:
public class Comment {
/**
* 孩子数量
*/
private List<Comment> children;
/**
* 展示内容
*/
private String content;
/**
* 是否可见,父节点收起后,子节点需要隐藏
*/
private boolean isVisible = true;
/**
* 当前节点是否收起状态
*/
private boolean isExpand = true;
/**
* 是否需要还原状态,父节点收起后会影响到子节点的状态
*/
private boolean isNeedReduction = false;
/**
* 当前节点等级,根据等级不同绘制竖线
*/
private int level;
public boolean isNeedReduction() {
return isNeedReduction;
}
public void setNeedReduction(boolean needReduction) {
isNeedReduction = needReduction;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public boolean isExpand() {
return isExpand;
}
public void setExpand(boolean expand) {
isExpand = expand;
}
public List<Comment> getChildren() {
return children;
}
public void setChildren(List<Comment> children) {
this.children = children;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isVisible() {
return isVisible;
}
public void setVisible(boolean visible) {
isVisible = visible;
}
}
伪造数据,此数据是嵌套的树状结构数据,显示时需要转换:
private List<Comment> initComment() {
List<Comment> comments1 = new ArrayList<>();
List<Comment> comments2 = new ArrayList<>();
Comment comment = new Comment();
comment.setChildren(new ArrayList<Comment>());
comment.setContent("可爱又迷人的反派角色");
comments2.add(comment);
List<Comment> comments3 = new ArrayList<>();
Comment comment1 = new Comment();
comment1.setChildren(comments2);
comment1.setContent("贯彻爱与真实的邪恶");
comments3.add(comment1);
List<Comment> comments4 = new ArrayList<>();
Comment comment2 = new Comment();
comment2.setChildren(comments3);
comment2.setContent("为了保护世界的和平");
comments4.add(comment2);
List<Comment> comments5 = new ArrayList<>();
Comment comment3 = new Comment();
comment3.setChildren(comments4);
comment3.setContent("为了防止世界被破坏");
comments5.add(comment3);
Comment comment4 = new Comment();
comment4.setChildren(new ArrayList<Comment>());
comment4.setContent("武藏!小次郎!");
comments5.add(comment4);
Comment comment5 = new Comment();
comment5.setChildren(new ArrayList<Comment>());
comment5.setContent("喵!");
comments5.add(comment5);
Comment comment6 = new Comment();
comment6.setChildren(comments5);
comment6.setContent("我们就大发慈悲的回答你");
comments1.add(comment6);
return comments1;
}
转换数据,最重要的一步:
private void convertComment(List<Comment> comments, int level) {
for (int i = 0; i < comments.size(); i++) {
Comment comment = comments.get(i);
//数据转换时,层数计算,默认为1
comment.setLevel(level);
comment.setVisible(true);
mComments.add(comment);
if (comment.getChildren() != null && comment.getChildren().size() > 0) {
//当前节点的子节点,比当前节点层数+1
convertComment(comment.getChildren(), level + 1);
}
}
}
构建ViewHolder,这里需要2类ViewHolder,一个是正常显示内容的VH,一类是隐藏内容的VH(当父节点收起时,子节点需要隐藏)
-
正常显示内容的VH
public class CommentVH extends RecyclerView.ViewHolder { private ImageView mCommentAnimteIv; private LinearLayout mCommentLl; private TextView mCommentDescTv; public CommentVH(View itemView) { super(itemView); mCommentAnimteIv = (ImageView) itemView.findViewById(R.id.comment_animate_iv); mCommentDescTv = (TextView) itemView.findViewById(R.id.comment_desc_tv); } public void update(Comment comment, Context context) { mCommentDescTv.setText(comment.getContent()); mCommentLl = (LinearLayout) itemView; if (mCommentLl.getChildCount() > 2) { mCommentLl.removeViews(0, mCommentLl.getChildCount() - 2); } //根据节点等级增加竖线 if (comment.getLevel() > 1) { RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); LayoutInflater inflater = LayoutInflater.from(context); for (int j = 1; j < comment.getLevel(); j++) { View lineView = inflater.inflate(R.layout.line_view, null); mCommentLl.addView(lineView, 0, layoutParams); } } } //评论缩放处理 public void addZoomListener(final TreeOperationListener treeOperationListener, final int pos) { if (mCommentAnimteIv != null) { mCommentAnimteIv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { treeOperationListener.zoom(pos); } }); } } //增加评论处理 public void addAddListener(final TreeOperationListener treeOperationListener, final int pos) { if (mCommentLl != null) { mCommentLl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { treeOperationListener.add(pos); } }); } } }
基础布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:orientation="vertical">
<ImageView
android:id="@+id/comment_animate_iv"
android:layout_width="12dp"
android:layout_height="14dp"
android:src="@drawable/comment_direction"/>
<View
android:id="@+id/comment_divider_view"
android:layout_width="1px"
android:layout_height="match_parent"
android:layout_marginLeft="6dp"
android:background="#cccccc"/>
</LinearLayout>
<RelativeLayout
android:id="@+id/comment_desc_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:layout_marginLeft="6dp">
<TextView
android:id="@+id/comment_more_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#a39b9b"
android:visibility="gone"
android:text="点击加载更多回复"/>
<TextView
android:id="@+id/comment_desc_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textSize="14sp"
android:textColor="#272727"
android:text="为了防止世界被破坏"/>
<LinearLayout
android:id="@+id/comment_info_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/comment_desc_tv"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<TextView
android:id="@+id/comment_nickname_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#a39b9b"
android:text="全麦面包不加糖·"/>
<TextView
android:id="@+id/comment_like_count_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#a39b9b"
android:text="100"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:src="@drawable/dislike"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
竖线布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="#cccccc"/>
</FrameLayout>
-
隐藏内容的VH
public class NullVH extends RecyclerView.ViewHolder { public NullVH(View itemView) { super(itemView); } }
到这里,基本算开发完成了,还要更深入的开发,就是给评论增加收起、放大功能,评论增加、删除了,原理也同样是数据转换。
源码