實現支援語音的評論功能
介紹
這篇文章,主要介紹是,在專案中,開發一個評論功能,並且支援語音功能的評論。
直接看效果圖:
功能分析與實現
簡單的說,需求就是:
實現 文字 或者 語音 評論,回覆只支援文字
為了下面更好的分析,這裡標註了一些用詞,如圖:
Comment:通過下面輸入框直接釋出的評論
SubComment:Comment下的回覆;
Public:在SubComment資料中,對Comment回覆(即點選右上角留言圖示),屬於Public狀態;
Private:在SubComment資料中,對SubComment回覆(即在下面點選使用者名稱回覆),屬於Private狀態;
有了這些規則,開始吧…
首先,先來實現,只有文字的評論。
文字評論實現
原始碼後面給出
通過上面的效果圖片,簡單分析下(如果分析的不清楚,可以看原始碼):
-
首先,可以看到,評論是一個List,這裡使用RecycleView來實現,而且後面還要新增語音的item,這裡使用RecycleView會更方便;
-
其次,整體佈局,主要是 SubComment (回覆)的佈局實現,其他比較簡單。這裡不饒彎子,如果使用過
SpannableStringBuilder
的童鞋,馬上就知道了。這個類可以說TextView的花式用法。這個類可以將一行textView的字串,切割成不同形式(區域性有顏色、區域性新增點選事件…),然後再拼接起來。(如果不懂具體的使用,我這裡單獨抽出來了,可以看看:但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;
}
-
然後,就是新增一些圖示的功能,釋出評論,點贊、回覆、刪除(如果是自己發表的評論),這些比較簡單了,可以直接看原始碼。
-
大體介面功能實現之後,為了增強使用者的體驗:點選某條評論,鍵盤上移的同時,其對應的內容也上移或下移,使其剛好在鍵盤的上方。
主要是給跟佈局添加布局監聽,然後根據點選那個位置,計算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個可以實現:
- MediaRecorder
- 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
…