实现支持语音的评论功能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pzm1993/article/details/83270546

介绍

这篇文章,主要介绍是,在项目中,开发一个评论功能,并且支持语音功能的评论。
直接看效果图:

图片描述

功能分析与实现

简单的说,需求就是:

实现 文本 或者 语音 评论,回复只支持文本

为了下面更好的分析,这里标注了一些用词,如图:

图片描述

Comment:通过下面输入框直接发布的评论
SubComment:Comment下的回复;
Public:在SubComment数据中,对Comment回复(即点击右上角留言图标),属于Public状态;
Private:在SubComment数据中,对SubComment回复(即在下面点击用户名回复),属于Private状态;

有了这些规则,开始吧…

首先,先来实现,只有文本的评论。

文本评论实现

源码后面给出

通过上面的效果图片,简单分析下(如果分析的不清楚,可以看源码):

  1. 首先,可以看到,评论是一个List,这里使用RecycleView来实现,而且后面还要添加语音的item,这里使用RecycleView会更方便;

  2. 其次,整体布局,主要是 SubComment (回复)的布局实现,其他比较简单。这里不饶弯子,如果使用过 SpannableStringBuilder 的童鞋,马上就知道了。这个类可以说TextView的花式用法。这个类可以将一行textView的字符串,切割成不同形式(局部有颜色、局部添加点击事件…),然后再拼接起来。(如果不懂具体的使用,我这里单独抽出来了,可以看看:SpannableString 和 SpannableStringBuilder的使用)

    扫描二维码关注公众号,回复: 4583655 查看本文章

    但SubComment的数量是动态的,这里自定义一个类CommentListView,使用addView()方法动态添加。主要代码如下:

/**
 * 数据刷新
 */
public void notifyDataSetChanged() {
	removeAllViews();
	if (mDatas == null || mDatas.size() == 0) {
		return;
	}
	LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
	
	for (int i = 0; i < mDatas.size(); i++) {
		final int index = i;
		/**
		 * 跟据SubComment的数量 ,生成多个的TextView
		 */
		View view = getView(index);
		if (view == null) {
			throw new NullPointerException("listview item layout is null, please check getView()...");
		}
		if (index != mDatas.size() - 1) {
			layoutParams.bottomMargin = DensityUtil.dpTopx(mContext, 5);
		}
		/**
		 * 添加到viewGroup中
		 */
		addView(view, index, layoutParams);
	}
	
}

/**
 *根据SubComment数据,生成textView
 */
private View getView(final int position) {
	if (layoutInflater == null) {
		layoutInflater = LayoutInflater.from(getContext());
	}
	View convertView = layoutInflater.inflate(R.layout.item_sub_comment_layout, null, false);
	
	TextView commentTv = convertView.findViewById(R.id.tv_sub_comment);
	final SubCommentData bean = mDatas.get(position);
	if (bean != null) {
		// 谁 回复
		User whoReplyUser = bean.getWhoReply();
		if (whoReplyUser != null && !StringUtil.isNull(whoReplyUser.getUserId())) {
			String whoReplyName = whoReplyUser.getNickname();
			int id = bean.getPostId();
			SpannableStringBuilder builder = new SpannableStringBuilder();
			builder.append(setClickableSpan(whoReplyName, whoReplyUser.getUserId()));
			
			// 回复 谁
			User replyWhoUser = bean.getReplyWho();
			if (replyWhoUser != null && !StringUtil.isNull(replyWhoUser.getUserId())) {
				String replyWhoName = replyWhoUser.getNickname();
				builder.append(mReplayStr);
				builder.append(setClickableSpan(replyWhoName, replyWhoUser.getUserId()));
			}
			builder.append(": ");
			
			String contentBodyStr = bean.getContent();
			
			builder.append(contentBodyStr);
			
			commentTv.setText(builder);
			final MovementMethod circleMovementMethod = new com.example.recordcomment.widget.MovementMethod(mItemSelectorBgColor, mItemSelectorBgColor);
			commentTv.setMovementMethod(circleMovementMethod);
			commentTv.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					if (circleMovementMethod.isPassToTv()) {
						if (onItemClickListener != null) {
							onItemClickListener.onItemClick(position);
							Toast.makeText(getContext(), "onClick", Toast.LENGTH_SHORT).show();
						}
					}
				}
			});
			commentTv.setOnLongClickListener(new OnLongClickListener() {
				@Override
				public boolean onLongClick(View v) {
					if (circleMovementMethod.isPassToTv()) {
						if (onItemLongClickListener != null) {
							onItemLongClickListener.onItemLongClick(position);
							Toast.makeText(getContext(), "onLongClick", Toast.LENGTH_SHORT).show();
						}
					}
					/**
					 * 返回 false ,让他触发MovementMethod的 onTouch(),最终使背景消失
					 */
					return false;
				}
			});
		}
	}
	return convertView;
}
  1. 然后,就是添加一些图标的功能,发布评论,点赞、回复、删除(如果是自己发表的评论),这些比较简单了,可以直接看源码。

  2. 大体界面功能实现之后,为了增强用户的体验:点击某条评论,键盘上移的同时,其对应的内容也上移或下移,使其刚好在键盘的上方。

主要是给跟布局添加布局监听,然后根据点击那个位置,计算recycleView的偏移量:

	/**
	 * 监听布局的变化,键盘
	 */
	private void setViewTreeObserver() {
		
		final ViewTreeObserver viewTreeObserver = mRootLayout.getViewTreeObserver();
		viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
			@Override
			public void onGlobalLayout() {
				Rect r = new Rect();
				/**
				 * 获取当前窗口可视区域大小的
				 */
				mRootLayout.getWindowVisibleDisplayFrame(r);
				int statusBarH = StatusUtil.getStatusBarHeight(getApplicationContext());// 状态栏高度
				int screenH = mRootLayout.getRootView().getHeight();
				if (r.top != statusBarH) {
					/**
					 * 在沉浸式状态栏时r.top=0; 如果有显示状态栏,在不计算状态栏高度
					 * r.top代表的是状态栏高度
					 */
					r.top = statusBarH;
				}
				int keyboardH = screenH - (r.bottom - r.top);
				if (keyboardH == mCurrentKeyboardHeight) { // 有变化时才处理,否则会陷入死循环
					return;
				}
				mCurrentKeyboardHeight = keyboardH;
				mScreenHeight = screenH;// 应用屏幕的高度
				mInputCommentHeight = mInputCommentLayout.getHeight();//底部输入框高度
				
				if (keyboardH < 150) {// 说明是隐藏键盘的情况
					hideSoftInput();
					return;
				}
				// 偏移listview
				if (mLayoutManager != null && mCommentConfig != null) {
					mLayoutManager.scrollToPositionWithOffset(mCommentConfig.commentPosition, getListViewOffset(mCommentConfig));
				}
			}
		});
	}

好了,大体上,文本评论的功能,主要就是这些了,具体细节,还是看源码。

语音评论实现

实现完了文本,接下来看看语音的实现。

语音评论,貌似比较少见,其实借鉴微信语音的节目功能,来模仿实现:

图片描述

分析下:
使用录音功能,主要有2个可以实现:

  1. MediaRecorder
  2. AudioRecord

关于这2个类的使用,网上也很多,这里就不啰嗦了。主要简单分析下,在项目的选择。

MediaRecorder

已集成了录音,编码,压缩等,所以使用比较简单,代码量少,录制的音频大小相对小很多,官方还有小demo。但,正是,放大了优点,缺点也比较明显,无法方便处理音频,输出的音频格式少,目前支持 .aac.amr.3gp

AudioRecord

语音的实时处理,可以用代码实现各种音频的封装,可以转换为wav格式,并且可以使用 lamelib 工具装换为MP3 格式。但是,代码量很多,需要AudioTrack进行处理,最终才能播放,并且录制之后的音频大小比较大。

咋一看,那肯定选择 MediaRecorder?但是,最终我们选择了 AudioRecord。

主要一个原因就是(非常坑),需要兼容IOS,统一音频格式。MediaRecorder的格式,IOS播放不了,所以最终转换为大家共同的兼容的格式:MP3
Android端可以使用lamelib 工具装换,并且,最终的音频大小也很小,所以,最终就采取了。

当然,随着技术的改变,可能后面有不同的选择,如果,大家有更好的方案,欢迎提出!!!

源码里面,我把这个2个类的实现多添加上,是不是很nice???这里就不贴代码了。。。
记得添加权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

关于仿微信界面,主要参考:
https://blog.csdn.net/lhk147852369/article/details/78658055

文本、语音合并

评论的内容有2种,所以在Comment这个位置的视图,需要切换。
当然,最简单的使用 setVisibility()实现也是可以的,但在过程中,处理起来不太方便,后面放弃了…
前面有提到,使用RecycleView,那么就是使用RecycleView的 getItemViewType()的功能来实现动态切换。

@Override
	public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) 
	    BaseCommentViewHolder holder = (BaseCommentViewHolder) viewHolder;
	    CommentData commentData = mCommentDataList.get(position);
        mCommentPosition = position;
	    if(commentData.getUser() == null) {
	        return;
	    }
        /**
         * 设置不同留言类型
         */
        switch (commentData.getContentType()) {
            case CONTENT_TYPE_TEXT :
                if(holder instanceof TextCommentViewHolder) {
                    ((TextCommentViewHolder)holder).contentText.setText(commentData.getContent());
                }
                break;
            case CONTENT_TYPE_RECORD :
                if(holder instanceof RecordCommentViewHolder) {
                    //todo
                    ((RecordCommentViewHolder)holder).audioPlaybackView.setDuration(Integer.parseInt(commentData.getVoiceTime()));
                    ((RecordCommentViewHolder)holder).audioPlaybackView.setRecordFile(commentData.getVoice());
                    setAudioPlaybackView((RecordCommentViewHolder) holder, position);
                }
                break;
            default:
                break;
        }
        //其他
        ......
	}

......
    /**
	 * @param position
	 * @return 返回留言内容的类型
	 */
	@Override
	public int getItemViewType(int position) {
		if (mCommentDataList != null && mCommentDataList.size() > 0) {
			int type = mCommentDataList.get(position).getContentType();
			if (type == CONTENT_TYPE_TEXT) {
				return CONTENT_TYPE_TEXT;
			} else if (type == CONTENT_TYPE_RECORD) {
				return CONTENT_TYPE_RECORD;
			}
		}
		return CONTENT_TYPE_TEXT;
	}

好了,简单就是这样子,具体还是看源码。

总结

好了,总体下来,并没有很困难的地方,问题不大。
So,有什么问题,欢迎一起讨论呢。

遇到的一些问题:

0.如何实现格式转换,并且将音频大小压缩
2.添加文本、或者语音不同评论时,如何处理更方便(即文本、语音合并)
3.播放语音时,处理由recycleView视图复用,导致的动画混乱;

源码点 这里


欢迎点赞

参考:

https://developer.android.google.cn/guide/topics/media/mediarecorder#java

http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html

https://blog.csdn.net/lhk147852369/article/details/78658055

猜你喜欢

转载自blog.csdn.net/pzm1993/article/details/83270546
今日推荐