使用开源项目CommentView
实现评论楼的效果(包含上传图片视频)
项目作者的详解地址,看一下这个基础介绍先哦
https://blog.csdn.net/qq_33794872/article/details/107163983
效果如下:
首先需要俩个布局,一个是发布者的布局,一个是评论者的布局:
发布者布局文件
custom_item_comment.xml
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ico"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="14dp"
android:layout_marginTop="18dp"
android:src="@drawable/bac1" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginRight="14dp"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textColor="@color/error_red"
android:textStyle="bold"
android:textSize="13sp" />
<ImageView
android:id="@+id/comment_item_like"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:padding="3dp"
android:src="@drawable/selector_pinglun"
android:visibility="visible" />
<TextView
android:id="@+id/prizes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textColor="#000000"
android:visibility="gone"
android:textSize="13sp" />
<TextView
android:id="@+id/data"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="24dp"
android:lineSpacingExtra="3dp"
android:layout_marginBottom="5dp"
android:textColor="#DFDDDD" />
<TextView
android:id="@+id/all_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全部回复:"
android:textColor="@color/text_gray"
android:layout_below="@+id/fl"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
/>
<FrameLayout
android:id="@+id/fl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/data"
>
<ImageView
android:id="@+id/comment_item_img"
android:layout_width="300dp"
android:layout_height="200dp"
android:background="@drawable/jiazai"
android:scaleType="fitXY"
android:layout_marginTop="5dp"
android:layout_marginLeft="5dp"
/>
<com.yc.video.player.VideoPlayer
android:id="@+id/video"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_marginTop="5dp"
android:layout_marginLeft="5dp"
/>
</FrameLayout>
</RelativeLayout>
</LinearLayout>
评论者的布局文件
custom_item_reply.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/reply_rootView"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="45dp"
android:background="@drawable/shape_reply"
android:layout_marginRight="25dp"
android:orientation="horizontal">
<View
android:id="@+id/line"
android:layout_width="2dp"
android:layout_height="wrap_content"
android:layout_alignTop="@id/right"
android:layout_alignBottom="@id/right"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:background="#c3c8cb" />
<RelativeLayout
android:id="@+id/right"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginRight="14dp"
android:layout_marginBottom="4dp"
android:layout_toRightOf="@id/line">
<TextView
android:id="@+id/user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:textColor="#2196F3"
android:textSize="13sp" />
<ImageView
android:id="@+id/comment_item_like"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:padding="3dp"
android:src="@drawable/pxjh"
android:visibility="gone" />
<TextView
android:id="@+id/prizes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textColor="#000000"
android:visibility="gone"
android:textSize="13sp" />
<TextView
android:id="@+id/data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginTop="22dp"
android:textColor="#ffffff" />
</RelativeLayout>
</RelativeLayout>
</FrameLayout>
</LinearLayout>
接下来就是显示View
写一个
Fragment
,我这里命名为JavaFragment
public class JavaFragment extends BaseFragment {
private static final String TAG = "JavaFragment";
private CommentView commentView;
private Gson gson;
private LocalServer localServer;
private ActivityHandler activityHandler;
private MainActivity activity;
public JavaFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
View view = inflater.inflate(R.layout.fragment_wai_guo_yu, container, false);
initComment(view, "android", activity);
return view;
}
/**
* 初始化 每个页面的 CommentView
*
* @param containerView Fragment 的容器view
* @param jsonPackageName 每个页面的对应的json包名
* @param activity
* @return
*/
public CommentView initComment(View containerView, String jsonPackageName, Activity activity) {
gson = new Gson();
commentView = containerView.findViewById(R.id.commentView);
localServer = new LocalServer(activity, jsonPackageName);
activityHandler = new ActivityHandler((MainActivity) getActivity(), commentView, gson);
//设置空视图
//commentView.setEmptyView(view);
//设置错误视图
//commentView.setErrorView(view);
//添加控件头布局
// commentView.addHeaderView();
commentView.setViewStyleConfigurator(new CustomViewStyleConfigurator(getContext()));
CommentView.CallbackBuilder callbackBuilder = commentView.callbackBuilder();
// 自定义评论布局(必须使用ViewHolder机制)--CustomCommentItemCallback<C> 泛型C为自定义评论数据类
callbackBuilder.customCommentItem(new CustomCommentItemCallback<CustomCommentModel.CustomComment>() {
@Override
public View buildCommentItem(int groupPosition, CustomCommentModel.CustomComment comment,
LayoutInflater inflater, View convertView, ViewGroup parent) {
//使用方法就像adapter里面的getView()方法一样
final CustomCommentViewHolder holder;
if (convertView == null) {
//使用自定义布局
convertView = inflater.inflate(R.layout.custom_item_comment, parent, false);
holder = new CustomCommentViewHolder(convertView);
//必须使用ViewHolder机制
convertView.setTag(holder);
} else {
holder = (CustomCommentViewHolder) convertView.getTag();
}
holder.userName.setText(comment.getPosterName());
// 设置头像
Glide.with(getContext())
.load(comment.getHeadUrl())
.into(holder.ico);
// 点击进行对该帖子进行 回复
if (listener != null) {
listener.hide();
}
holder.comment_item_like.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 插入评论
//commentView.addReply(reply(""), groupPosition);
CommentInfoDialog dialog = new CommentInfoDialog.Builder(activity)
.create(commentView, groupPosition, -1, activity, JavaFragment.this);
dialog.show();
}
});
// 设置帖子具体内容
holder.comment.setText(comment.getData());
// 没有回复者就隐藏“全部回复:”字样
if (comment.getReplies().size() == 0) {
holder.all_comment.setVisibility(View.GONE);
} else {
holder.all_comment.setVisibility(View.VISIBLE);
}
if (comment.getTag() == 1) {
holder.frameLayout.setVisibility(View.VISIBLE);
holder.commentImg.setVisibility(View.VISIBLE);
holder.videoPlayer.setVisibility(View.INVISIBLE);
// 加载图片
// 根据评论的图片url是否为空来判断是否隐藏图片或者显示图片
//Log.d(TAG, "yanshuweibuildCommentItem: " + comment.getUrl());
if (!comment.getUrl().equals("")) {
// holder.commentImg.setImageBitmap(getBitmap(comment.getUrl()));
Glide.with(getContext())
.load(comment.getUrl())
.into(holder.commentImg);
} else {
holder.commentImg.setVisibility(View.GONE);
}
} else if (comment.getTag() == -1) {
holder.frameLayout.setVisibility(View.VISIBLE);
holder.videoPlayer.setVisibility(View.VISIBLE);
holder.commentImg.setVisibility(View.INVISIBLE);
// 设置视频
//创建基础视频播放器,一般播放器的功能
BasisVideoController controller = new BasisVideoController(getContext());
//设置控制器
holder.videoPlayer.setController(controller);
//设置视频播放链接地址
holder.videoPlayer.release();
holder.videoPlayer.setUrl(comment.getUrl());
//开始播放
//holder.videoPlayer.start();
//holder.videoPlayer.startTinyScreen();
} else {
holder.frameLayout.setVisibility(View.GONE);
}
return convertView;
}
});
//自定义评论布局(必须使用ViewHolder机制)
// 并且自定义ViewHolder类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder
// --CustomReplyItemCallback<R> 泛型R为自定义回复数据类
callbackBuilder.customReplyItem(new CustomReplyItemCallback<CustomCommentModel.CustomComment.CustomReply>() {
@Override
public View buildReplyItem(int groupPosition, int childPosition, boolean isLastReply, CustomCommentModel.CustomComment.CustomReply reply, LayoutInflater inflater, View convertView, ViewGroup parent) {
boolean isCommentNull = ((CustomCommentModel.CustomComment)commentView.getCommentList().get(groupPosition)) == null;
List<CustomCommentModel.CustomComment> list = (List<CustomCommentModel.CustomComment>) commentView.getCommentList();
//使用方法就像adapter里面的getView()方法一样
//此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错
CustomReplyViewHolder holder = null;
//此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错
if (convertView == null) {
//使用自定义布局(回复布局)
convertView = inflater.inflate(R.layout.custom_item_reply, parent, false);
holder = new CustomReplyViewHolder(convertView);
//必须使用ViewHolder机制
convertView.setTag(holder);
} else {
holder = (CustomReplyViewHolder) convertView.getTag();
}
holder.userName.setText(reply.getReplierName());
holder.reply.setText(reply.getData());
return convertView;
}
});
//下拉刷新回调
/*.setOnPullRefreshCallback(new MyOnPullRefreshCallback())*/
//评论、回复Item的点击回调(点击事件回调)
callbackBuilder.setOnItemClickCallback(new MyOnItemClickCallback())
//回复数据加载更多回调(加载更多回复)
.setOnReplyLoadMoreCallback(new MyOnReplyLoadMoreCallback())
//上拉加载更多回调(加载更多评论数据)
.setOnCommentLoadMoreCallback(new MyOnCommentLoadMoreCallback())
//设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效
.buildCallback();
// 首次加载贴子数据
load(1, 1);
return commentView;
}
/**
* 加载评论数据
*
* @param code
* 1: 首次加载和刷新
* 2: 加载第二页
* 3: 加载第三页
* 4:加载第一页的更多回复
* 5:加载第三页的更多回复(第二页)
* 6:加载第三页的更多回复(第二页)
* @param handlerId
*/
public void load(int code,int handlerId){
localServer.get(code,activityHandler,handlerId);
}
/**
* 获取CommentView
*
* @return
*/
public CommentView getCommentView(){
return commentView;
}
/**
* 上拉加载更多回调类
*/
private class MyOnCommentLoadMoreCallback implements OnCommentLoadMoreCallback {
@Override
public void loading(int currentPage, int willLoadPage, boolean isLoadedAllPages) {
// willLoadPage == json里面的“nextPage”值
Log.d(TAG, "loading: " + "当前页是第" + currentPage + " 即将加载第" + willLoadPage);
// handlerId == 3 是 处理 上拉加载更多 消息
if (!isLoadedAllPages){
if(willLoadPage==2){
load(2,3);
}else if(willLoadPage==3){
load(3,3);
}else if(willLoadPage == 4){
load(7, 3);
}
}
}
@Override
public void complete() {
//加载完成后的操作
}
@Override
public void failure(String msg) {
}
}
/**
* 回复加载更多回调类
*/
private class MyOnReplyLoadMoreCallback implements OnReplyLoadMoreCallback<CustomCommentModel.CustomComment.CustomReply> {
@Override
public void loading(CustomCommentModel.CustomComment.CustomReply reply, int willLoadPage) {
if(willLoadPage==2){
load(5,4);
}else if(willLoadPage==3){
load(6,4);
}
}
@Override
public void complete() {
}
@Override
public void failure(String msg) {
}
}
/**
* 点击回调
*/
private class MyOnItemClickCallback implements OnItemClickCallback<CustomCommentModel.CustomComment, CustomCommentModel.CustomComment.CustomReply> {
@Override
public void commentItemOnClick(int position, CustomCommentModel.CustomComment comment, View view) {
}
@Override
public void replyItemOnClick(int c_position, int r_position, CustomCommentModel.CustomComment.CustomReply reply, View view) {
}
}
/**
* 下拉刷新回调类
*/
private class MyOnPullRefreshCallback implements OnPullRefreshCallback {
@Override
public void refreshing() {
load(1,2);
XToast.showSucceed("刷新完成");
}
@Override
public void complete() {
//加载完成后的操作
XToast.showSucceed("加载完成");
}
@Override
public void failure(String msg) {
}
}
需要一个弹窗来发布或者评论信息
/**
* <>
* 评论信息收集对话框
* </>
*/
public class CommentInfoDialog extends Dialog {
private static final String TAG = "CommentInfoDialog";
public CommentInfoDialog(@NonNull Context context) {
super(context, R.style.single_dialog);
}
public CommentInfoDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
public CommentInfoDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public static class Builder{
private Context context;
public Builder(Context context) {
this.context = context;
}
public CommentInfoDialog create(CommentView commentView, int position, int childPositon, Activity activity, JavaFragment fragment){
CommentInfoDialog dialog = new CommentInfoDialog(context);
View view = View.inflate(context, R.layout.layout_viewpoint_dilog, null);
initDialogEvent(commentView, view, dialog, position, activity, fragment);
setXDialogAttributes(dialog, view);
return dialog;
}
// 是否点击上传图片和视频
private boolean isPostMedia = false;
/**
* 设置数据
*/
private void initDialogEvent(CommentView commentView, View view,
CommentInfoDialog dialog, int position,
Activity activity, JavaFragment fragment) {
ImageView back, post_img, post_video, selected_img;
EditText data;
TextView commit;
back = view.findViewById(R.id._spot_back);
commit = view.findViewById(R.id.comment_commit);
data = view.findViewById(R.id.comment_data);
post_img = view.findViewById(R.id.img);
post_video = view.findViewById(R.id.img_video);
selected_img = view.findViewById(R.id.select_img);
/**
* 点击选择上传的图片
*/
post_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isPostMedia = true;
Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
activity.startActivityForResult(intent, 2);
}
});
/**
* 点击选择上传的视频
*/
post_video.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isPostMedia = true;
Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/*");
activity.startActivityForResult(intent, 3);
}
});
/**
* 显示选择好的图片
*/
((MainActivity)activity).setOnSetSelectedImagePathListener(new MainActivity.OnSetSelectedImagePathListener() {
@Override
public void setPath(String path, boolean isImage) {
if(isImage) {
// 显示选择的图片
selected_img.setImageBitmap(getBitmap(path));
}else {
// 显示选择的视频缩略图
// Log.d(TAG, "setPath: " + path);
selected_img.setImageBitmap(getVideoBitmap(path));
}
}
});
// 判断是否隐藏选择图片/视频按钮
if (fragment != null) {
fragment.setHideListener(new JavaFragment.HideListener() {
@Override
public void hide() {
post_img.setVisibility(View.INVISIBLE);
post_video.setVisibility(View.INVISIBLE);
}
});
}
commit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发帖 position == -1,点击悬浮按钮触发
if(position == -1 && !data.getText().toString().trim().equals("")) {
post_img.setVisibility(View.VISIBLE);
post_video.setVisibility(View.VISIBLE);
CustomCommentModel.CustomComment newComment = new CustomCommentModel.CustomComment();
MediaUri mediaUri = MainActivity.mediaUri;
if (isPostMedia) {
newComment.setPosterName(UserUtil.readName());
newComment.setData(data.getText().toString());
if(!UserUtil.getUserPic().equals("")){
newComment.setHeadUrl(UserUtil.getUserPic());
}else {
// 使用默认头像
newComment.setHeadUrl("");
}
newComment.setTag(mediaUri.getTag());
newComment.setUrl(mediaUri.getUrl());
} else {
newComment.setPosterName(UserUtil.readName());
newComment.setData(data.getText().toString());
if(!UserUtil.getUserPic().equals("")){
newComment.setHeadUrl(UserUtil.getUserPic());
}else {
// 使用默认头像
newComment.setHeadUrl("");
}
newComment.setTag(0);
newComment.setUrl("");
}
List<CustomCommentModel.CustomComment.CustomReply> list = new ArrayList<>();
newComment.setReplies(list);
// for (int i = 0; i < list.size(); i++){
// Log.d(TAG, "发贴子操作 :[回复者]:" + list.get(i).getReplierName() + "<回复内容>:" + list.get(i).getData());
// }
commentView.addComment(newComment);
isPostMedia = false;
dialog.dismiss();
}else if(!data.getText().toString().trim().equals("")){
// int count = list == null ? 0 : list.size();
// Log.d(TAG, "onClick: 回复者数量 == " + count + " 回复位置 == " + position);
// 点击子项的 “评论” 触发:回复
CustomCommentModel.CustomComment.CustomReply reply = new CustomCommentModel.CustomComment.CustomReply();
reply.setReplierName(UserUtil.readName());
reply.setData(data.getText().toString());
//Log.d(TAG, "onClick: " + "回复者:" + reply.getReplierName() + ", 回复内容:" + reply.getData());
commentView.addReply(reply, position);
dialog.dismiss();
}else {
XToast.showError("内容不能为空!");
}
}
});
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
private Bitmap getBitmap(String path) {
File file = new File(path);
try {
FileInputStream in = new FileInputStream(file);
try {
return BitmapFactory.decodeFileDescriptor(in.getFD());
} catch (IOException e) {
Log.e("mxdmxd", "IOException" + e.toString());
}
} catch (FileNotFoundException e) {
Log.e("mxdmxd", "FileNotFoundException" + e.toString());
}
return null;
}
/**
* 通过视频的url解析出视频的缩率图
*
* @param url 视频的地址
* @return 需要的缩略图
*/
private Bitmap getVideoBitmap(String url) {
Log.i(TAG, "getVideoBitmap!");
File file = new File(url);
try {
FileInputStream in = new FileInputStream(file);
try {
FileDescriptor fd = in.getFD();
MediaMetadataRetriever media = new MediaMetadataRetriever();
media.setDataSource(fd);
return media.getFrameAtTime();
} catch (IOException e) {
Log.e(TAG, "getVideoBitmap failed IOException" + e.toString());
}
} catch (FileNotFoundException e) {
Log.e(TAG, "getVideoBitmap failed FileNotFoundException" + e.toString());
}
return null;
}
/**
* 设置属性,动画
* @param dialog
* @param view
*/
protected void setXDialogAttributes(CommentInfoDialog dialog, View view){
Window window = dialog.getWindow();
// 设置对话框的padding
window.getDecorView().setPadding(0,0,0,0);
WindowManager.LayoutParams lp = window.getAttributes();
// 对话框的宽度
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
// 对话框的高度
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(lp);
// 设置对话框的在屏幕中的位置,这里是居中
window.setGravity(Gravity.CENTER);
// 设置对话框弹入退出动画
window.setWindowAnimations(R.style.dialog_enter_and_exit_animation_1);
dialog.setContentView(view);
dialog.setCanceledOnTouchOutside(true);
dialog.setCancelable(true);
}
}
}