布局Activity:
public class DiscussAty extends BaseAty implements CommentExpandAdapter.DiscussTextViewOnclick,OnClickListener{ private ImageView image_back; private TextView editText; private ExpandableListView commentExpandableListView; private CommentExpandAdapter commentExpandAdapter; private List<CommentDetailBean> mList = new ArrayList<>(); private BottomSheetDialog dialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_discuss_aty); initView(); } private void initView() { image_back = findViewById(R.id.discuss_pinglun_back); editText = findViewById(R.id.discuss_pinglun_edit); commentExpandableListView = findViewById(R.id.discuss_commentexpandalelistview); mList = generateTestData(); initExpandableListView(mList); image_back.setOnClickListener(this); editText.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.discuss_pinglun_back) { finish(); } else if (id == R.id.discuss_pinglun_edit) { showCommentDialog(); } } /** * 初始化评论和回复列表 */ private void initExpandableListView(final List<CommentDetailBean> commentList){ commentExpandableListView.setGroupIndicator(null); commentExpandAdapter = new CommentExpandAdapter(this, commentList,this); commentExpandableListView.setAdapter(commentExpandAdapter); //默认展开所有回复 int size = commentList.size(); for(int i = 0; i<size; i++){ commentExpandableListView.expandGroup(i); } commentExpandableListView.setOnGroupClickListener(new OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { return true; } }); commentExpandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { return false; } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(item.getItemId() == android.R.id.home){ finish(); return true; } return super.onOptionsItemSelected(item); } /** * by moos on 2018/04/20 * func:生成测试数据 * @return 评论数据 */ private List<CommentDetailBean> generateTestData(){ List<CommentDetailBean> commentList = new ArrayList<>(); CommentDetailBean bean1 = new CommentDetailBean("程序员","2021年4月16日15:46:04就是这个点我开始装逼了。","五分钟前"); List<ReplyDetailBean> list_bean1 = new ArrayList<>(); ReplyDetailBean reply_bean1 = new ReplyDetailBean("沐风","时间总是在不经意间溜走了,你啊你啊个腿的。"); ReplyDetailBean reply_bean2 = new ReplyDetailBean("狗子","狗子的时间总是在不经意间溜走了,你啊你啊个腿的。"); ReplyDetailBean reply_bean3 = new ReplyDetailBean("晓明","晓明的时间总是在不经意间溜走了,你啊你啊个腿的。"); ReplyDetailBean reply_bean4 = new ReplyDetailBean("Baby","Baby的时间总是在不经意间溜走了,你啊你啊个腿的。"); ReplyDetailBean reply_bean5 = new ReplyDetailBean("大黄","大黄的时间总是在不经意间溜走了,你啊你啊个腿的。"); list_bean1.add(reply_bean1); list_bean1.add(reply_bean2); list_bean1.add(reply_bean3); list_bean1.add(reply_bean4); list_bean1.add(reply_bean5); bean1.setReplyList(list_bean1); CommentDetailBean bean2 = new CommentDetailBean("产品狗","22021年4月16日15:59:36就是这个点我开始装逼了。","五分钟前"); List<ReplyDetailBean> list_bean2 = new ArrayList<>(); ReplyDetailBean reply_bean6 = new ReplyDetailBean("沐风","时间总是在不经意间溜走了,你啊你啊个腿的。"); ReplyDetailBean reply_bean7 = new ReplyDetailBean("狗子","狗子的时间总是在不经意间溜走了,你啊你啊个腿的。"); list_bean2.add(reply_bean6); list_bean2.add(reply_bean7); bean2.setReplyList(list_bean2); commentList.add(bean1); commentList.add(bean2); return commentList; } /** * by moos on 2018/04/20 * func:弹出评论框 */ private void showCommentDialog(){ dialog = new BottomSheetDialog(this); View commentView = LayoutInflater.from(this).inflate(R.layout.comment_dialog_layout,null); final EditText commentText = (EditText) commentView.findViewById(R.id.dialog_comment_et); final TextView bt_comment = commentView.findViewById(R.id.dialog_comment_bt); dialog.setContentView(commentView); /** * 解决bsd显示不全的情况 */ View parent = (View) commentView.getParent(); BottomSheetBehavior behavior = BottomSheetBehavior.from(parent); commentView.measure(0,0); behavior.setPeekHeight(commentView.getMeasuredHeight()); bt_comment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String commentContent = commentText.getText().toString().trim(); if(!TextUtils.isEmpty(commentContent)){ //commentOnWork(commentContent); dialog.dismiss(); CommentDetailBean detailBean = new CommentDetailBean("小明", commentContent,"刚刚"); commentExpandAdapter.addTheCommentData(detailBean); Toast.makeText(DiscussAty.this,"评论成功",Toast.LENGTH_SHORT).show(); }else { Toast.makeText(DiscussAty.this,"评论内容不能为空",Toast.LENGTH_SHORT).show(); } } }); commentText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { if(!TextUtils.isEmpty(charSequence) && charSequence.length()>2){ bt_comment.setBackgroundColor(Color.parseColor("#FFB568")); }else { bt_comment.setBackgroundColor(Color.parseColor("#D8D8D8")); } } @Override public void afterTextChanged(Editable editable) { } }); dialog.show(); } /** * by moos on 2018/04/20 * func:弹出回复框 */ private void showReplyDialog(final int position,final int childposition){ dialog = new BottomSheetDialog(this); View commentView = LayoutInflater.from(this).inflate(R.layout.comment_dialog_layout,null); final EditText commentText = (EditText) commentView.findViewById(R.id.dialog_comment_et); final TextView bt_comment = commentView.findViewById(R.id.dialog_comment_bt); if(childposition>=0){ commentText.setHint("回复 " + mList.get(position).getReplyList().get(childposition).getNickName() + " 的评论:"); }else { commentText.setHint("回复 " + mList.get(position).getNickName() + " 的评论:"); } dialog.setContentView(commentView); bt_comment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String replyContent = commentText.getText().toString().trim(); if(!TextUtils.isEmpty(replyContent)){ dialog.dismiss(); ReplyDetailBean detailBean = new ReplyDetailBean("小红"+position+childposition,replyContent); commentExpandAdapter.addTheReplyData(detailBean, position); commentExpandableListView.expandGroup(position); Toast.makeText(DiscussAty.this,"回复成功",Toast.LENGTH_SHORT).show(); }else { Toast.makeText(DiscussAty.this,"回复内容不能为空",Toast.LENGTH_SHORT).show(); } } }); commentText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { if(!TextUtils.isEmpty(charSequence) && charSequence.length()>2){ bt_comment.setBackgroundColor(Color.parseColor("#FFB568")); }else { bt_comment.setBackgroundColor(Color.parseColor("#D8D8D8")); } } @Override public void afterTextChanged(Editable editable) { } }); dialog.show(); } /** * 弹出举报框 * @param groupPosition */ private void showReportDialog(final int groupPosition){ dialog = new BottomSheetDialog(this); View commentView = LayoutInflater.from(this).inflate(R.layout.comment_report_dialog,null); LinearLayout layout_copy = commentView.findViewById(R.id.report_dialog_copy); LinearLayout layout_pingbi = commentView.findViewById(R.id.report_dialog_pingbi); LinearLayout layout_report = commentView.findViewById(R.id.report_dialog_report); dialog.setContentView(commentView); layout_copy.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(DiscussAty.this,"复制文字",Toast.LENGTH_SHORT).show(); } }); layout_pingbi.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(DiscussAty.this,"屏蔽拉黑",Toast.LENGTH_SHORT).show(); } }); layout_report.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(DiscussAty.this,"举报",Toast.LENGTH_SHORT).show(); } }); dialog.show(); } @Override public void User_icon(int groupPosition, int childPosition) { if(childPosition>=0){ mList.get(groupPosition).getReplyList().get(childPosition).getUserLogo(); }else { mList.get(groupPosition).getUserLogo(); } showCommentDialog(); } @Override public void HuiFuClick(int groupPosition,int childPosition) { showReplyDialog(groupPosition,childPosition); } @Override public void DianDianClick(int groupPosition) { showReportDialog(groupPosition); } @Override public void ShowMore(int groupPosition) { Toast.makeText(DiscussAty.this,"Position:"+groupPosition,Toast.LENGTH_LONG).show(); commentExpandableListView.expandGroup(groupPosition); } }
布局很简单就是一个ExpandListView,大家测试的时候可以把Activity中没有的view删了
重点来了,就是Adapter的使用
public class CommentExpandAdapter extends BaseExpandableListAdapter{ private static final String TAG = "CommentExpandAdapter"; boolean isLike = false; int line_num = 1;//展示几行 private List<CommentDetailBean> commentBeanList; private Context context; private DiscussTextViewOnclick discussTextViewOnclick; // private boolean maxIs3=true;//子View最多3个 public CommentExpandAdapter(Context context, List<CommentDetailBean> commentBeanList,DiscussTextViewOnclick click) { this.context = context; this.commentBeanList = commentBeanList; this.discussTextViewOnclick = click; } @Override public int getGroupCount() { return commentBeanList.size(); } @Override public int getChildrenCount(int groupPosition) { /* *通过对父布局中是否展开判断,从而刷新页面知道是否展开评论 */ CommentDetailBean commentDetailBean = commentBeanList.get(groupPosition); int size = commentDetailBean.getReplyList().size(); int size1=Math.min(size,3); int size2=Math.max(size,size1); boolean isOpen = commentDetailBean.isOpen(); if(isOpen){ return size1; }else{ return size2; } } @Override public Object getGroup(int i) { return commentBeanList.get(i); } @Override public Object getChild(int i, int i1) { return commentBeanList.get(i).getReplyList().get(i1); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return getCombinedChildId(groupPosition, childPosition); } @Override public boolean hasStableIds() { return true; } @Override public View getGroupView(final int groupPosition, boolean isExpand, View convertView, ViewGroup viewGroup) { final GroupHolder groupHolder; if(convertView == null){ convertView = LayoutInflater.from(context).inflate(R.layout.comment_item_layout, viewGroup, false); groupHolder = new GroupHolder(convertView); convertView.setTag(groupHolder); }else { groupHolder = (GroupHolder) convertView.getTag(); } Glide.with(context).load(R.drawable.user_other) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)//RESULT .error(R.drawable.ic_launcher) .centerCrop() .into(groupHolder.logo); groupHolder.tv_name.setText(commentBeanList.get(groupPosition).getNickName()); groupHolder.tv_time.setText(commentBeanList.get(groupPosition).getCreateDate()); String content = commentBeanList.get(groupPosition).getContent(); groupHolder.collapsedTextView.setMovementMethod(Common_Utils.MyLinkedMovementMethod.getInstance()); int lines = Common_Utils.CountLines(content); SpannableStringBuilder stringBuilder = Common_Utils.SpanTextMessage(context,"",content);//缩放后的 if(lines>line_num){//判断点击事件 stringBuilder.setSpan(Common_Utils.setClickSpan(context,"",content,stringBuilder,groupHolder.collapsedTextView), stringBuilder.length()-4,stringBuilder.length(),SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); } groupHolder.collapsedTextView.setText(stringBuilder); return convertView; } @Override public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup viewGroup) { if(convertView == null){ convertView = LayoutInflater.from(context).inflate(R.layout.comment_reply_item_layout,viewGroup, false); } CommentDetailBean commentDetailBean = commentBeanList.get(groupPosition); List<ReplyDetailBean> replyList = commentDetailBean.getReplyList(); String nickName = replyList.get(childPosition).getNickName(); String content = replyList.get(childPosition).getContent(); TextView text_nickName = convertView.findViewById(R.id.reply_item_userName); ImageView logo = convertView.findViewById(R.id.reply_item_logo); Glide.with(context).load(R.drawable.user_other) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)//RESULT .error(R.drawable.ic_launcher) .centerCrop() .into(logo); LinearLayout linearLayout = convertView.findViewById(R.id.reply_item_layout); TextView text_huifu = convertView.findViewById(R.id.reply_item_huifu); TextView text_more = convertView.findViewById(R.id.reply_item_more); TextView text_content = convertView.findViewById(R.id.reply_item_content); TextView text_time = convertView.findViewById(R.id.reply_item_time); TextView text_floor = convertView.findViewById(R.id.reply_item_floor); ImageView image_like = (ImageView)convertView.findViewById(R.id.reply_item_like); text_nickName.setText(nickName); text_time.setText(commentBeanList.get(groupPosition).getCreateDate()); text_floor.setText("2楼"); //TextView展开全文显示的地方 text_content.setMovementMethod(Common_Utils.MyLinkedMovementMethod.getInstance()); String mContent; if(!TextUtils.isEmpty(nickName)){ mContent = "回复"+nickName+":"+content; }else { mContent = content; } int lines = Common_Utils.CountLines(content); SpannableStringBuilder stringBuilder = Common_Utils.SpanTextMessage(context,nickName,content);//缩放后的 if(lines>line_num){ stringBuilder.setSpan(Common_Utils.setClickSpan(context,nickName,mContent,stringBuilder,text_content), stringBuilder.length()-4,stringBuilder.length(),SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); } text_content.setText(stringBuilder); /** * 展示更多回复 */ if(isLastChild&&replyList.size()>3){ text_more.setVisibility(View.VISIBLE); }else { text_more.setVisibility(View.GONE); } if(commentDetailBean.isOpen()){ text_more.setText("展开更多评论 >"); }else { text_more.setText("收起更多"); } text_more.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean open = commentDetailBean.isOpen(); open = !open; commentDetailBean.setOpen(open); notifyDataSetChanged(); } }); return convertView; } @Override public boolean isChildSelectable(int i, int i1) { return true; } //父布局 private class GroupHolder{ private CircleImageView logo; private TextView collapsedTextView; private LinearLayout linearLayout; private TextView tv_name, tv_content, tv_time,tv_huifu,tv_diandian,tv_floor; private ImageView iv_like; public GroupHolder(View view) { linearLayout = view.findViewById(R.id.comment_item_layout); logo = (CircleImageView) view.findViewById(R.id.comment_item_logo); collapsedTextView = view.findViewById(R.id.comment_item_content); tv_name = (TextView) view.findViewById(R.id.comment_item_userName); tv_time = (TextView) view.findViewById(R.id.comment_item_time); iv_like = (ImageView) view.findViewById(R.id.comment_item_like); tv_huifu = view.findViewById(R.id.comment_item_huifu); tv_diandian = view.findViewById(R.id.comment_item_diandian); } } //子布局 private class ChildHolder{ private CircleImageView logo; private TextView collapsedTextView; private LinearLayout linearLayout; private TextView tv_name, tv_content, tv_time,tv_huifu,tv_floor,tv_more; private ImageView iv_like; public ChildHolder(View view) { linearLayout = view.findViewById(R.id.reply_item_layout); tv_floor = view.findViewById(R.id.reply_item_floor); logo = (CircleImageView) view.findViewById(R.id.reply_item_logo); collapsedTextView = view.findViewById(R.id.reply_item_content); tv_name = (TextView) view.findViewById(R.id.reply_item_userName); tv_time = (TextView) view.findViewById(R.id.reply_item_time); iv_like = (ImageView) view.findViewById(R.id.reply_item_like); tv_huifu = view.findViewById(R.id.reply_item_huifu); } } /** * by moos on 2018/04/20 * func:评论成功后插入一条数据 * @param commentDetailBean 新的评论数据 */ public void addTheCommentData(CommentDetailBean commentDetailBean){ if(commentDetailBean!=null){ commentBeanList.add(commentDetailBean); notifyDataSetChanged(); }else { throw new IllegalArgumentException("评论数据为空!"); } } /** * by moos on 2018/04/20 * func:回复成功后插入一条数据 * @param replyDetailBean 新的回复数据 */ public void addTheReplyData(ReplyDetailBean replyDetailBean, int groupPosition){ if(replyDetailBean!=null){ Log.e(TAG, "addTheReplyData: >>>>该刷新回复列表了:"+replyDetailBean.toString() ); if(commentBeanList.get(groupPosition).getReplyList() != null ){ commentBeanList.get(groupPosition).getReplyList().add(replyDetailBean); }else { List<ReplyDetailBean> replyList = new ArrayList<>(); replyList.add(replyDetailBean); commentBeanList.get(groupPosition).setReplyList(replyList); } notifyDataSetChanged(); }else { throw new IllegalArgumentException("回复数据为空!"); } } /** * by moos on 2018/04/20 * func:添加和展示所有回复 * @param replyBeanList 所有回复数据 * @param groupPosition 当前的评论 */ private void addReplyList(List<ReplyDetailBean> replyBeanList, int groupPosition){ if(commentBeanList.get(groupPosition).getReplyList() != null ){ commentBeanList.get(groupPosition).getReplyList().clear(); commentBeanList.get(groupPosition).getReplyList().addAll(replyBeanList); }else { commentBeanList.get(groupPosition).setReplyList(replyBeanList); } notifyDataSetChanged(); } //单条以及回复按钮点击事件 public interface DiscussTextViewOnclick{ void User_icon(int groupPosition,int childPosition); void HuiFuClick(int groupPosition,int childPosition); void DianDianClick(int groupPosition); void ShowMore(int groupPosition); } }
因为害怕子布局会出现复用问题导致点击出现问题,这里子布局我就没用复用,大家可以自己试试
/** * SpanTextMessage处理TextView中部分名字显示以及显示更多问题,18就是一行多少个汉字,这里一开始想用TextView的宽度来判断有多少行的,奈何textView.getWidth()方法返回0我也就懒得再去调,直接写死18了 span.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.blue)), 2, nickName.length()+2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);是让名字显示蓝色 */ public static SpannableStringBuilder SpanTextMessage(Context context,String nickName, String content){ String mContent = ""; SpannableStringBuilder span; if(!TextUtils.isEmpty(nickName)){ mContent = "回复"+nickName+":"+content; }else { mContent = content; } int lines = CountLines(mContent); span = new SpannableStringBuilder(mContent); if(lines>line_num){ span.replace(0,span.length(),span.subSequence(0,line_num*18-7)).append(span_open); if(!TextUtils.isEmpty(nickName)){ span.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.blue)), 2, nickName.length()+2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } }else { if(!TextUtils.isEmpty(nickName)){ span.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.blue)), 2, nickName.length()+2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } } return span; }
//处理文字展开更多的点击事件问题,这里span_close,span_open可以自己定义汉字collapsed就是用来是否展开的自己定义一下就可以
//我一开始的想法错了,也是借鉴文章的问题,后来找同事才setClickSpan这个方法这样才对
public static ClickableSpan setClickSpan(Context context,String nickName, String content, SpannableStringBuilder stringBuilder, TextView textView){ ClickableSpan clickableSpan = new ClickableSpan() { @Override public void onClick(@NonNull View widget) { if (collapsed){ stringBuilder.replace(0,stringBuilder.length(),content).append(span_close); if(!TextUtils.isEmpty(nickName)){ stringBuilder.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.blue)), 2, nickName.length()+2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } stringBuilder.setSpan(setClickSpan(context,nickName,content,stringBuilder,textView), stringBuilder.length()-2,stringBuilder.length(),SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(stringBuilder); }else { stringBuilder.replace(0,stringBuilder.length(),content.substring(0,18*line_num-7)).append(span_open); if(!TextUtils.isEmpty(nickName)){ stringBuilder.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.blue)), 2, nickName.length()+2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } stringBuilder.setSpan(setClickSpan(context,nickName,content,stringBuilder,textView), stringBuilder.length()-4,stringBuilder.length(),SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(stringBuilder); } collapsed = !collapsed; } }; return clickableSpan; }
//这个也是一个博主写的文章,具体文章链接找不到了,抱歉,这个主要是处理TextView展开更多与单条点击冲突问题
public static class MyLinkedMovementMethod extends LinkMovementMethod { private static MyLinkedMovementMethod sInstance; public static MyLinkedMovementMethod getInstance() { if (sInstance == null) sInstance = new MyLinkedMovementMethod(); return sInstance; } @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { // 因为TextView没有点击事件,所以点击TextView的非富文本时,super.onTouchEvent()返回false; // 此时可以让TextView的父容器执行点击事件; boolean isConsume = super.onTouchEvent(widget, buffer, event); if (!isConsume && event.getAction() == MotionEvent.ACTION_UP) { ViewParent parent = widget.getParent(); if (parent instanceof ViewGroup) { // 获取被点击控件的父容器,让父容器执行点击; ((ViewGroup) parent).performClick(); } } return isConsume; } }
以上方法借鉴了好几个博主的方法,然后自己又修改之后完善的最终的评论项目,因为涉及到别的问题,github代码就不上传了,核心代码已经给大家贴出来了。