Commit dd175e595daff5b537a20766b78627f028edffc5

Authored by 王琳
1 parent 7aca54d0

聊天室添加发送图片以及语音消息

Showing 32 changed files with 940 additions and 155 deletions
app/libs/libcnliveutil.jar
No preview for this file type
app/src/main/AndroidManifest.xml
@@ -2,7 +2,6 @@ @@ -2,7 +2,6 @@
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.cnlive.im"> 3 package="com.cnlive.im">
4 4
5 -  
6 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 5 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7 <uses-permission android:name="android.permission.INTERNET" /> 6 <uses-permission android:name="android.permission.INTERNET" />
8 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 7 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -17,44 +16,37 @@ @@ -17,44 +16,37 @@
17 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 16 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
18 17
19 <application 18 <application
20 - android:name="com.cnlive.im.CNLiveApplication" 19 + android:name=".CNLiveApplication"
21 android:allowBackup="true" 20 android:allowBackup="true"
22 android:icon="@mipmap/ic_launcher" 21 android:icon="@mipmap/ic_launcher"
23 android:label="@string/app_name" 22 android:label="@string/app_name"
24 android:supportsRtl="true" 23 android:supportsRtl="true"
25 android:theme="@style/AppTheme"> 24 android:theme="@style/AppTheme">
26 - <activity android:name="com.cnlive.im.ui.MainActivity"> 25 + <activity android:name=".ui.MainActivity">
27 <intent-filter> 26 <intent-filter>
28 <action android:name="android.intent.action.MAIN" /> 27 <action android:name="android.intent.action.MAIN" />
29 28
30 <category android:name="android.intent.category.LAUNCHER" /> 29 <category android:name="android.intent.category.LAUNCHER" />
31 </intent-filter> 30 </intent-filter>
32 </activity> 31 </activity>
33 -  
34 -  
35 <activity 32 <activity
36 - android:name="com.cnlive.im.ui.ChatRoomActivity" 33 + android:name=".ui.ChatRoomActivity"
37 android:theme="@style/Theme.AppCompat.Light.NoActionBar" 34 android:theme="@style/Theme.AppCompat.Light.NoActionBar"
38 - android:windowSoftInputMode="adjustResize"></activity>  
39 - 35 + android:windowSoftInputMode="adjustResize" />
40 <activity 36 <activity
41 - android:name="com.cnlive.im.ui.LoGinActivity" 37 + android:name=".ui.LoGinActivity"
42 android:screenOrientation="portrait" /> 38 android:screenOrientation="portrait" />
43 -  
44 <activity 39 <activity
45 android:name=".ui.UserLoginActivity" 40 android:name=".ui.UserLoginActivity"
46 android:screenOrientation="portrait" /> 41 android:screenOrientation="portrait" />
47 -  
48 <activity 42 <activity
49 - android:name="com.cnlive.im.ui.CommentActivity" 43 + android:name=".ui.CommentActivity"
50 android:screenOrientation="portrait" /> 44 android:screenOrientation="portrait" />
51 45
52 -  
53 <service 46 <service
54 android:name="io.rong.imlib.ipc.RongService" 47 android:name="io.rong.imlib.ipc.RongService"
55 android:exported="true" 48 android:exported="true"
56 android:process=":ipc" /> 49 android:process=":ipc" />
57 -  
58 <service 50 <service
59 android:name="io.rong.imlib.ReConnectService" 51 android:name="io.rong.imlib.ReConnectService"
60 android:exported="true" /> 52 android:exported="true" />
@@ -62,10 +54,10 @@ @@ -62,10 +54,10 @@
62 <receiver 54 <receiver
63 android:name="io.rong.imlib.ConnectChangeReceiver" 55 android:name="io.rong.imlib.ConnectChangeReceiver"
64 android:exported="true" /> 56 android:exported="true" />
65 -  
66 <receiver 57 <receiver
67 android:name="io.rong.imlib.HeartbeatReceiver" 58 android:name="io.rong.imlib.HeartbeatReceiver"
68 android:process=":ipc" /> 59 android:process=":ipc" />
  60 +
69 </application> 61 </application>
70 62
71 </manifest> 63 </manifest>
72 \ No newline at end of file 64 \ No newline at end of file
app/src/main/java/com/cnlive/im/adapter/ChatRoomAdpter.java
1 package com.cnlive.im.adapter; 1 package com.cnlive.im.adapter;
2 2
3 import android.content.Context; 3 import android.content.Context;
  4 +import android.database.Cursor;
4 import android.graphics.Color; 5 import android.graphics.Color;
5 import android.net.Uri; 6 import android.net.Uri;
  7 +import android.provider.ContactsContract;
  8 +import android.provider.MediaStore;
6 import android.support.v7.widget.RecyclerView; 9 import android.support.v7.widget.RecyclerView;
  10 +import android.util.Log;
7 import android.view.LayoutInflater; 11 import android.view.LayoutInflater;
8 import android.view.View; 12 import android.view.View;
9 import android.view.ViewGroup; 13 import android.view.ViewGroup;
  14 +import android.view.WindowManager;
10 import android.widget.ImageView; 15 import android.widget.ImageView;
  16 +import android.widget.LinearLayout;
11 import android.widget.TextView; 17 import android.widget.TextView;
12 18
13 import com.bumptech.glide.Glide; 19 import com.bumptech.glide.Glide;
@@ -18,6 +24,12 @@ import com.cnlive.im.mode.ImagMessage; @@ -18,6 +24,12 @@ import com.cnlive.im.mode.ImagMessage;
18 import com.cnlive.im.mode.StringMessage; 24 import com.cnlive.im.mode.StringMessage;
19 import com.cnlive.im.mode.VoiceMessage; 25 import com.cnlive.im.mode.VoiceMessage;
20 26
  27 +import java.text.SimpleDateFormat;
  28 +import java.util.Calendar;
  29 +import java.util.Date;
  30 +import java.util.HashMap;
  31 +import java.util.Locale;
  32 +
21 /** 33 /**
22 * @author 陈硕 34 * @author 陈硕
23 * @time 2017/7/14 14:35 35 * @time 2017/7/14 14:35
@@ -100,6 +112,9 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter { @@ -100,6 +112,9 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter {
100 Object o = mItems.get(position); 112 Object o = mItems.get(position);
101 ImagMessage o1 = (ImagMessage) o; 113 ImagMessage o1 = (ImagMessage) o;
102 holder1.ima_user_name.setTextColor(Color.parseColor("#d61d0c")); 114 holder1.ima_user_name.setTextColor(Color.parseColor("#d61d0c"));
  115 + holder1.img_content.getLayoutParams().height = 200;
  116 + holder1.img_content.getLayoutParams().width = 200;
  117 + holder1.img_content.setLayoutParams(holder1.img_content.getLayoutParams());
103 Glide.with(mContext).load(o1.getCnImgMessage().getRemoteUri()).into(holder1.img_content); 118 Glide.with(mContext).load(o1.getCnImgMessage().getRemoteUri()).into(holder1.img_content);
104 holder1.ima_user_name.setText(o1.getCnImgMessage().getUserInfo().getUserName()); 119 holder1.ima_user_name.setText(o1.getCnImgMessage().getUserInfo().getUserName());
105 } else if (holder instanceof VoiceMessageHolder) { 120 } else if (holder instanceof VoiceMessageHolder) {
@@ -108,6 +123,8 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter { @@ -108,6 +123,8 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter {
108 VoiceMessage o1 = (VoiceMessage) o; 123 VoiceMessage o1 = (VoiceMessage) o;
109 holder1.voice_user_name.setText(o1.getCnVoiceMessage().getUserInfo().getUserName()); 124 holder1.voice_user_name.setText(o1.getCnVoiceMessage().getUserInfo().getUserName());
110 final Uri voiceUri = o1.getCnVoiceMessage().getVoiceUri(); 125 final Uri voiceUri = o1.getCnVoiceMessage().getVoiceUri();
  126 + int duration = o1.getCnVoiceMessage().getVoiceTime();
  127 + holder1.voice_duration.setText(String.valueOf(duration).concat("\""));
111 holder1.itemView.setOnClickListener(new View.OnClickListener() { 128 holder1.itemView.setOnClickListener(new View.OnClickListener() {
112 @Override 129 @Override
113 public void onClick(View v) { 130 public void onClick(View v) {
@@ -146,15 +163,15 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter { @@ -146,15 +163,15 @@ public class ChatRoomAdpter extends BaseRecyclerAdapter {
146 163
147 164
148 private TextView voice_user_name; 165 private TextView voice_user_name;
149 - private ImageView voice_content; 166 + private TextView voice_content;
  167 + private TextView voice_duration;
150 168
151 public VoiceMessageHolder(View itemView) { 169 public VoiceMessageHolder(View itemView) {
152 super(itemView); 170 super(itemView);
153 voice_user_name = (TextView) itemView.findViewById(R.id.voice_user_name); 171 voice_user_name = (TextView) itemView.findViewById(R.id.voice_user_name);
154 - voice_content = (ImageView) itemView.findViewById(R.id.voice_content); 172 + voice_content = (TextView) itemView.findViewById(R.id.voice_content);
  173 + voice_duration = (TextView) itemView.findViewById(R.id.voice_duration);
155 } 174 }
156 } 175 }
157 176
158 -  
159 -  
160 } 177 }
app/src/main/java/com/cnlive/im/ui/ChatRoomActivity.java
@@ -2,23 +2,37 @@ package com.cnlive.im.ui; @@ -2,23 +2,37 @@ package com.cnlive.im.ui;
2 2
3 import android.Manifest; 3 import android.Manifest;
4 import android.app.Activity; 4 import android.app.Activity;
  5 +import android.content.ContentResolver;
  6 +import android.content.Context;
5 import android.content.Intent; 7 import android.content.Intent;
6 import android.content.pm.PackageManager; 8 import android.content.pm.PackageManager;
  9 +import android.database.Cursor;
7 import android.net.Uri; 10 import android.net.Uri;
8 import android.os.Build; 11 import android.os.Build;
9 import android.os.Bundle; 12 import android.os.Bundle;
10 import android.os.Handler; 13 import android.os.Handler;
  14 +import android.provider.ContactsContract;
  15 +import android.provider.MediaStore;
  16 +import android.support.annotation.NonNull;
  17 +import android.support.v4.app.ActivityCompat;
11 import android.support.v4.content.ContextCompat; 18 import android.support.v4.content.ContextCompat;
12 import android.support.v7.app.AppCompatActivity; 19 import android.support.v7.app.AppCompatActivity;
13 import android.support.v7.widget.LinearLayoutManager; 20 import android.support.v7.widget.LinearLayoutManager;
14 import android.support.v7.widget.RecyclerView; 21 import android.support.v7.widget.RecyclerView;
  22 +import android.text.Editable;
  23 +import android.text.TextUtils;
  24 +import android.text.TextWatcher;
15 import android.util.Log; 25 import android.util.Log;
  26 +import android.view.Gravity;
16 import android.view.MotionEvent; 27 import android.view.MotionEvent;
17 import android.view.View; 28 import android.view.View;
  29 +import android.view.inputmethod.InputMethodManager;
18 import android.widget.Button; 30 import android.widget.Button;
19 import android.widget.EditText; 31 import android.widget.EditText;
  32 +import android.widget.ImageView;
20 import android.widget.LinearLayout; 33 import android.widget.LinearLayout;
21 import android.widget.RelativeLayout; 34 import android.widget.RelativeLayout;
  35 +import android.widget.TextView;
22 import android.widget.Toast; 36 import android.widget.Toast;
23 37
24 import com.cnlive.im.R; 38 import com.cnlive.im.R;
@@ -27,8 +41,11 @@ import com.cnlive.im.adapter.base.VoiceItemClickListener; @@ -27,8 +41,11 @@ import com.cnlive.im.adapter.base.VoiceItemClickListener;
27 import com.cnlive.im.mode.ImagMessage; 41 import com.cnlive.im.mode.ImagMessage;
28 import com.cnlive.im.mode.StringMessage; 42 import com.cnlive.im.mode.StringMessage;
29 import com.cnlive.im.mode.VoiceMessage; 43 import com.cnlive.im.mode.VoiceMessage;
  44 +import com.cnlive.im.util.AudioRecordUtil;
30 import com.cnlive.im.util.MediaRecorderUtil; 45 import com.cnlive.im.util.MediaRecorderUtil;
31 import com.cnlive.im.util.MessageType; 46 import com.cnlive.im.util.MessageType;
  47 +import com.cnlive.im.util.PlayerUtil;
  48 +import com.cnlive.im.util.TimeUtils;
32 import com.cnlive.libs.util.chat.ChatUtil; 49 import com.cnlive.libs.util.chat.ChatUtil;
33 import com.cnlive.libs.util.chat.base.IChat; 50 import com.cnlive.libs.util.chat.base.IChat;
34 import com.cnlive.libs.util.chat.model.CNImgMessage; 51 import com.cnlive.libs.util.chat.model.CNImgMessage;
@@ -38,15 +55,18 @@ import com.cnlive.libs.util.chat.model.CNVoiceMessage; @@ -38,15 +55,18 @@ import com.cnlive.libs.util.chat.model.CNVoiceMessage;
38 55
39 import org.json.JSONException; 56 import org.json.JSONException;
40 57
  58 +import java.io.LineNumberReader;
41 import java.util.ArrayList; 59 import java.util.ArrayList;
42 60
  61 +import static android.R.id.edit;
  62 +
43 63
44 /** 64 /**
45 * @author 陈硕 65 * @author 陈硕
46 * @time 2017/3/7 14:36 66 * @time 2017/3/7 14:36
47 * @desc ${TODD} 67 * @desc ${TODD}
48 */ 68 */
49 -public class ChatRoomActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener, VoiceItemClickListener { 69 +public class ChatRoomActivity extends AppCompatActivity implements View.OnClickListener, VoiceItemClickListener {
50 private static final String TAG = "ChatRoomActivity"; 70 private static final String TAG = "ChatRoomActivity";
51 private ArrayList<Object> groupChatOneListMy; 71 private ArrayList<Object> groupChatOneListMy;
52 private String roomIdOne = "ChatRoom05"; 72 private String roomIdOne = "ChatRoom05";
@@ -57,32 +77,55 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -57,32 +77,55 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
57 private Button foundChatBtn; 77 private Button foundChatBtn;
58 private Handler mHandler; 78 private Handler mHandler;
59 private RelativeLayout re_chat; 79 private RelativeLayout re_chat;
60 - private LinearLayout lin_user;  
61 - private EditText user_id, user_name; 80 +// private LinearLayout lin_user;
  81 +// private EditText user_id, user_name;
62 private Button exit_chat1; 82 private Button exit_chat1;
63 public static String user_name_string; 83 public static String user_name_string;
64 private boolean isUserLogin = false; 84 private boolean isUserLogin = false;
65 - private Button ima_send;  
66 - private Button voice_send; 85 + private ImageView ima_send;
  86 + private Button sendVoice;
  87 +
  88 + private Context context;
  89 + private AudioRecordUtil mAudioRecordUtil;
  90 + private PopupWindowFactory mPop;
  91 + private ImageView mImageView;
  92 + private TextView mTextView;
  93 + private ImageView keybordBtn;
  94 +
  95 + private LinearLayout sendMsgLayout;
  96 + private LinearLayout cancelSendLayout;
  97 +
  98 + private boolean isVoiceBtnVisible = false;
67 99
68 100
69 public static final int IMG_FROM_PICTURE = 0x1001; 101 public static final int IMG_FROM_PICTURE = 0x1001;
70 public static final int VOICE = 0x1002; 102 public static final int VOICE = 0x1002;
71 public static final int PICTURE_REQUEST_CODE = 0x1003; 103 public static final int PICTURE_REQUEST_CODE = 0x1003;
72 public static final int VOICE_REQUEST_CODE = 0x1004; 104 public static final int VOICE_REQUEST_CODE = 0x1004;
73 - private MediaRecorderUtil mediaRecorderUtil;  
74 private long l; 105 private long l;
75 int i; 106 int i;
76 107
  108 + private String mFilePath;
  109 +
  110 + private PlayerUtil playerUtil;
  111 +
  112 + // 所需的全部权限
  113 + static final String[] PERMISSIONS = new String[]{
  114 + Manifest.permission.RECORD_AUDIO,
  115 + Manifest.permission.WRITE_EXTERNAL_STORAGE
  116 + };
  117 +
77 @Override 118 @Override
78 protected void onCreate(Bundle savedInstanceState) { 119 protected void onCreate(Bundle savedInstanceState) {
79 super.onCreate(savedInstanceState); 120 super.onCreate(savedInstanceState);
80 //取消状态栏 121 //取消状态栏
81 setContentView(R.layout.activity_groupchat); 122 setContentView(R.layout.activity_groupchat);
  123 + context = this;
82 initUI(); 124 initUI();
  125 + mAudioRecordUtil = new AudioRecordUtil();
  126 + mAudioRecordUtil.setOnAudioStatusUpdateListener(onAudioStatusUpdateListener);
83 //设置监听 127 //设置监听
84 mHandler = new Handler(); 128 mHandler = new Handler();
85 - mediaRecorderUtil = new MediaRecorderUtil();  
86 ChatUtil.setOnConnectStatusListener(connectStatusListener); 129 ChatUtil.setOnConnectStatusListener(connectStatusListener);
87 ChatUtil.setOnChatRoomActionListener(chatRoomActionListener); 130 ChatUtil.setOnChatRoomActionListener(chatRoomActionListener);
88 ChatUtil.setOnReceiveMessageListener(receiveMessageListener); 131 ChatUtil.setOnReceiveMessageListener(receiveMessageListener);
@@ -93,21 +136,30 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -93,21 +136,30 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
93 if (user_name_string != null) 136 if (user_name_string != null)
94 ChatUtil.login(new CNUserInfo(user_id_string, user_name_string, ""), connectListener); 137 ChatUtil.login(new CNUserInfo(user_id_string, user_name_string, ""), connectListener);
95 else ChatUtil.logOut(connectListener); 138 else ChatUtil.logOut(connectListener);
  139 + requestPermission();
  140 + playerUtil = new PlayerUtil(this);
96 141
97 } 142 }
98 143
99 144
100 private void initUI() { 145 private void initUI() {
101 - voice_send = (Button) findViewById(R.id.voice_send);  
102 - ima_send = (Button) findViewById(R.id.ima_send); 146 +
  147 + final View view = View.inflate(this, R.layout.layout_microphone, null);
  148 + mPop = new PopupWindowFactory(this, view);
  149 + mImageView = (ImageView) view.findViewById(R.id.iv_recording_icon);
  150 + mTextView = (TextView) view.findViewById(R.id.tv_recording_time);
  151 + sendMsgLayout = (LinearLayout) view.findViewById(R.id.send_layout);
  152 + cancelSendLayout = (LinearLayout) view.findViewById(R.id.cancel_send_layout);
  153 + ima_send = (ImageView) findViewById(R.id.ima_send);
103 exit_chat1 = (Button) findViewById(R.id.exit_chat1); 154 exit_chat1 = (Button) findViewById(R.id.exit_chat1);
104 - user_id = (EditText) findViewById(R.id.user_ID);  
105 - user_name = (EditText) findViewById(R.id.user_name);  
106 - lin_user = (LinearLayout) findViewById(R.id.lin_user); 155 +// user_id = (EditText) findViewById(R.id.user_ID);
  156 +// user_name = (EditText) findViewById(R.id.user_name);
  157 +// lin_user = (LinearLayout) findViewById(R.id.lin_user);
107 re_chat = (RelativeLayout) findViewById(R.id.re_chat); 158 re_chat = (RelativeLayout) findViewById(R.id.re_chat);
108 foundChatBtn = (Button) findViewById(R.id.creatChat); 159 foundChatBtn = (Button) findViewById(R.id.creatChat);
109 listViewOne = (RecyclerView) findViewById(R.id.chat_list_viewOne); 160 listViewOne = (RecyclerView) findViewById(R.id.chat_list_viewOne);
110 editText = (EditText) findViewById(R.id.message_content); 161 editText = (EditText) findViewById(R.id.message_content);
  162 + editText.addTextChangedListener(textWatcher);
111 send_messageOneBtn = (Button) findViewById(R.id.send_messageOne); 163 send_messageOneBtn = (Button) findViewById(R.id.send_messageOne);
112 ima_send.setOnClickListener(this); 164 ima_send.setOnClickListener(this);
113 exit_chat1.setOnClickListener(this); 165 exit_chat1.setOnClickListener(this);
@@ -118,7 +170,15 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -118,7 +170,15 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
118 chatListAdapterOne.setonVoiceItemClickListener(this); 170 chatListAdapterOne.setonVoiceItemClickListener(this);
119 listViewOne.setLayoutManager(new LinearLayoutManager(ChatRoomActivity.this)); 171 listViewOne.setLayoutManager(new LinearLayoutManager(ChatRoomActivity.this));
120 listViewOne.setAdapter(chatListAdapterOne); 172 listViewOne.setAdapter(chatListAdapterOne);
121 - voice_send.setOnTouchListener(this); 173 +
  174 + sendVoice = (Button) findViewById(R.id.send_voice);
  175 + re_chat = (RelativeLayout) findViewById(R.id.activity_chat_room);
  176 + keybordBtn = (ImageView) findViewById(R.id.keybord);
  177 + keybordBtn.setOnClickListener(this);
  178 +
  179 +
  180 + StartListener();
  181 +
122 182
123 } 183 }
124 184
@@ -126,7 +186,6 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -126,7 +186,6 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
126 /** 186 /**
127 * 发送消息监听 187 * 发送消息监听
128 */ 188 */
129 -  
130 private IChat.OnSendMessageListener sendMessageListener = new IChat.OnSendMessageListener() { 189 private IChat.OnSendMessageListener sendMessageListener = new IChat.OnSendMessageListener() {
131 @Override 190 @Override
132 public void onAttached(Object o) { 191 public void onAttached(Object o) {
@@ -148,14 +207,20 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -148,14 +207,20 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
148 CNVoiceMessage o1 = (CNVoiceMessage) o; 207 CNVoiceMessage o1 = (CNVoiceMessage) o;
149 VoiceMessage voiceMessage = new VoiceMessage(MessageType.voiceMessage, o1, true); 208 VoiceMessage voiceMessage = new VoiceMessage(MessageType.voiceMessage, o1, true);
150 chatListAdapterOne.addItem(voiceMessage); 209 chatListAdapterOne.addItem(voiceMessage);
  210 + //发送成功后删除保存在本地的录制文件
  211 + mAudioRecordUtil.deleteRecordFile(mFilePath);
  212 +
151 } 213 }
  214 + listViewOne.smoothScrollToPosition(chatListAdapterOne.getItemCount() - 1);
152 } 215 }
153 }); 216 });
154 } 217 }
155 218
156 @Override 219 @Override
157 public void onError(Object o, int i, String s) { 220 public void onError(Object o, int i, String s) {
  221 + if (o instanceof CNImgMessage) {
158 222
  223 + }
159 } 224 }
160 }; 225 };
161 226
@@ -186,6 +251,7 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -186,6 +251,7 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
186 VoiceMessage voiceMessage = new VoiceMessage(MessageType.voiceMessage, o1, false); 251 VoiceMessage voiceMessage = new VoiceMessage(MessageType.voiceMessage, o1, false);
187 chatListAdapterOne.addItem(voiceMessage); 252 chatListAdapterOne.addItem(voiceMessage);
188 } 253 }
  254 + listViewOne.smoothScrollToPosition(chatListAdapterOne.getItemCount() - 1);
189 } 255 }
190 }); 256 });
191 257
@@ -282,41 +348,24 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -282,41 +348,24 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
282 case R.id.ima_send: 348 case R.id.ima_send:
283 fromPicture(); 349 fromPicture();
284 break; 350 break;
  351 + case R.id.keybord:
  352 + if (!isVoiceBtnVisible) {
  353 + editText.setVisibility(View.GONE);
  354 + sendVoice.setVisibility(View.VISIBLE);
  355 + keybordBtn.setImageResource(R.drawable.keybord);
  356 + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
  357 + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
  358 + isVoiceBtnVisible = true;
  359 + } else {
  360 + sendVoice.setVisibility(View.GONE);
  361 + editText.setVisibility(View.VISIBLE);
  362 + keybordBtn.setImageResource(R.drawable.voice);
  363 + isVoiceBtnVisible = false;
  364 + }
  365 + break;
285 } 366 }
286 } 367 }
287 368
288 - public void mediaRecorder() {  
289 - if (Build.VERSION.SDK_INT >= 23) {  
290 - int checkCallPhonePermission = ContextCompat.checkSelfPermission(ChatRoomActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);  
291 - int checkCallPhonePermission1 = ContextCompat.checkSelfPermission(ChatRoomActivity.this, Manifest.permission.RECORD_AUDIO);  
292 -  
293 - if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED || checkCallPhonePermission1 != PackageManager.PERMISSION_GRANTED) {  
294 - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, VOICE_REQUEST_CODE);  
295 - return;  
296 - } else {  
297 - recorder();  
298 - }  
299 - } else {  
300 - recorder();  
301 - }  
302 - }  
303 -  
304 -  
305 - public String getRecoderFile() {  
306 -  
307 - return mediaRecorderUtil.getRecorderFile();  
308 -  
309 - }  
310 -  
311 - public void recorder() {  
312 - mediaRecorderUtil.record();  
313 -  
314 - }  
315 -  
316 - public void releaseRecord() {  
317 - mediaRecorderUtil.releaseRecord();  
318 -  
319 - }  
320 369
321 public void fromPicture() { 370 public void fromPicture() {
322 if (Build.VERSION.SDK_INT >= 23) { 371 if (Build.VERSION.SDK_INT >= 23) {
@@ -336,31 +385,8 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -336,31 +385,8 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
336 private void toIntentPicture() { 385 private void toIntentPicture() {
337 Intent intent = new Intent(); 386 Intent intent = new Intent();
338 intent.setType("image/*"); 387 intent.setType("image/*");
339 - intent.setAction(Intent.ACTION_GET_CONTENT);  
340 - startActivityForResult(intent, IMG_FROM_PICTURE);  
341 - }  
342 -  
343 -  
344 - @Override  
345 - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {  
346 - super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
347 - switch (requestCode) {  
348 - case PICTURE_REQUEST_CODE:  
349 - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
350 - toIntentPicture();  
351 - } else {  
352 - Toast.makeText(ChatRoomActivity.this, "权限已被禁止,请打开,再使用此功能", Toast.LENGTH_LONG).show();  
353 - }  
354 - break;  
355 -  
356 - case VOICE_REQUEST_CODE:  
357 - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
358 - recorder();  
359 - } else {  
360 - Toast.makeText(ChatRoomActivity.this, "权限已被禁止,请打开,再使用此功能", Toast.LENGTH_LONG).show();  
361 - }  
362 - break;  
363 - } 388 + intent.setAction(Intent.ACTION_PICK);
  389 + startActivityForResult(Intent.createChooser(intent, "Complete action using"), IMG_FROM_PICTURE);
364 } 390 }
365 391
366 392
@@ -370,7 +396,19 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -370,7 +396,19 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
370 if (resultCode == Activity.RESULT_OK) { 396 if (resultCode == Activity.RESULT_OK) {
371 switch (requestCode) { 397 switch (requestCode) {
372 case IMG_FROM_PICTURE: 398 case IMG_FROM_PICTURE:
373 - Uri uri = data.getData(); 399 +// String localImagePath = "/sdcard/test.png";
  400 +// Uri selectedImage = Uri.parse("file://" + localImagePath);
  401 + Uri selectedImage = data.getData();
  402 + String[] filePathColumn = {MediaStore.Images.Media.DATA};
  403 +
  404 + Cursor cursor = getContentResolver().query(selectedImage,
  405 + filePathColumn, null, null, null);
  406 + cursor.moveToFirst();
  407 +
  408 + int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
  409 + final String picturePath = cursor.getString(columnIndex);
  410 +
  411 + Uri uri = Uri.parse("file://" + picturePath);
374 ChatUtil.sendImaMessage(IChat.CHATROOM, roomIdOne, uri, uri, sendMessageListener); 412 ChatUtil.sendImaMessage(IChat.CHATROOM, roomIdOne, uri, uri, sendMessageListener);
375 break; 413 break;
376 414
@@ -406,53 +444,169 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL @@ -406,53 +444,169 @@ public class ChatRoomActivity extends AppCompatActivity implements View.OnClickL
406 444
407 445
408 @Override 446 @Override
409 - public boolean onTouch(View v, MotionEvent event) { 447 + public void onItemClick(Uri uri) {
  448 + playerUtil.start(uri);
410 449
411 - switch (event.getAction()) {  
412 - case MotionEvent.ACTION_DOWN:  
413 - l = System.currentTimeMillis();  
414 - mediaRecorder();  
415 - break;  
416 - case MotionEvent.ACTION_UP: 450 + }
  451 +
  452 +
  453 + /**************************************************************************/
  454 +
  455 + /**
  456 + * 开启扫描之前判断权限是否打开
  457 + */
  458 + private void requestPermission() {
  459 + //判断是否开启摄像头权限
  460 + if ((ContextCompat.checkSelfPermission(context,
  461 + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) &&
  462 + (ContextCompat.checkSelfPermission(context,
  463 + Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED)
  464 + ) {
  465 + StartListener();
  466 +
  467 + //判断是否开启语音权限
  468 + } else {
  469 + //请求获取摄像头权限
  470 + ActivityCompat.requestPermissions((Activity) context,
  471 + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, VOICE_REQUEST_CODE);
  472 + }
  473 + }
417 474
418 - if (Build.VERSION.SDK_INT >= 23) {  
419 - int checkCallPhonePermission = ContextCompat.checkSelfPermission(ChatRoomActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);  
420 - int checkCallPhonePermission1 = ContextCompat.checkSelfPermission(ChatRoomActivity.this, Manifest.permission.RECORD_AUDIO); 475 + @Override
  476 + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  477 + super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  478 + if (requestCode == VOICE_REQUEST_CODE) {
  479 + if ((grantResults[0] == PackageManager.PERMISSION_GRANTED) && (grantResults[1] == PackageManager.PERMISSION_GRANTED)) {
  480 + StartListener();
  481 + } else {
  482 + Toast.makeText(context, "已拒绝权限!", Toast.LENGTH_SHORT).show();
  483 + }
  484 + }
  485 + }
  486 +
  487 + float downX;
  488 + float downY;
  489 +
  490 + public void StartListener() {
  491 +
  492 + //Button的touch监听
  493 + sendVoice.setOnTouchListener(new View.OnTouchListener() {
  494 + @Override
  495 + public boolean onTouch(View v, MotionEvent event) {
  496 + if (lacksPermissions(PERMISSIONS)) {
  497 + if (lacksPermission(PERMISSIONS[0])) {
  498 + Toast.makeText(ChatRoomActivity.this, "没有麦克风权限,请前往“设置 - 应用 - 权限”中开启权限", Toast.LENGTH_SHORT).show();
  499 +
  500 + } else if (lacksPermission(PERMISSIONS[1])) {
  501 + Toast.makeText(ChatRoomActivity.this, "没有文件读写权限,请前往“设置 - 应用 - 权限”中开启权限", Toast.LENGTH_SHORT).show();
421 502
422 - if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED || checkCallPhonePermission1 != PackageManager.PERMISSION_GRANTED) {  
423 - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, VOICE_REQUEST_CODE);  
424 - } else {  
425 - sendVoice();  
426 } 503 }
427 } else { 504 } else {
428 - sendVoice(); 505 +
  506 + switch (event.getAction()) {
  507 +
  508 + case MotionEvent.ACTION_DOWN:
  509 + mPop.showAtLocation(re_chat, Gravity.CENTER, 0, 0);
  510 +
  511 + sendVoice.setText("松开发送");
  512 + mAudioRecordUtil.startRecord();
  513 + downX = event.getX();
  514 + downY = event.getY();
  515 +
  516 + break;
  517 +
  518 + case MotionEvent.ACTION_UP:
  519 +
  520 + if (cancelSendLayout.getVisibility() == View.VISIBLE) {
  521 + cancelSendLayout.setVisibility(View.GONE);
  522 + sendMsgLayout.setVisibility(View.VISIBLE);
  523 + }
  524 + float moveX = Math.abs(event.getX() - downX);
  525 + float moveY = Math.abs(event.getY() - downY);
  526 + if (moveY > 100) {
  527 +// Toast.makeText(ChatRoomActivity.this, "cancel", Toast.LENGTH_SHORT).show();
  528 + mAudioRecordUtil.cancelRecord();
  529 + } else {
  530 + mAudioRecordUtil.stopRecord(); //结束录音(保存录音文件)
  531 + }
  532 + mPop.dismiss();
  533 + mTextView.setText("00:00");
  534 + sendVoice.setText("按住说话");
  535 + break;
  536 + case MotionEvent.ACTION_MOVE:
  537 + float moveXm = Math.abs(event.getX() - downX);
  538 + float moveYm = Math.abs(event.getY() - downY);
  539 + if (moveYm > 100) {
  540 +// Toast.makeText(ChatRoomActivity.this, "cancel", Toast.LENGTH_SHORT).show();
  541 + if (sendMsgLayout.getVisibility() == View.VISIBLE) {
  542 + sendMsgLayout.setVisibility(View.GONE);
  543 + cancelSendLayout.setVisibility(View.VISIBLE);
  544 + }
  545 + } else {
  546 + if (cancelSendLayout.getVisibility() == View.VISIBLE) {
  547 + cancelSendLayout.setVisibility(View.GONE);
  548 + sendMsgLayout.setVisibility(View.VISIBLE);
  549 + }
  550 + }
  551 + break;
  552 + }
429 } 553 }
  554 + return true;
  555 + }
  556 + });
430 557
  558 + }
431 559
432 - break; 560 + // 判断权限集合
  561 + public boolean lacksPermissions(String... permissions) {
  562 + for (String permission : permissions) {
  563 + if (lacksPermission(permission)) {
  564 + return true;
  565 + }
  566 + }
  567 + return false;
  568 + }
433 569
  570 + // 判断是否缺少权限
  571 + private boolean lacksPermission(String permission) {
  572 + return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED;
  573 + }
434 574
  575 + private AudioRecordUtil.OnAudioStatusUpdateListener onAudioStatusUpdateListener = new AudioRecordUtil.OnAudioStatusUpdateListener() {
  576 + @Override
  577 + public void onUpdate(double db, long time) {
  578 + mImageView.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
  579 + mTextView.setText(TimeUtils.long2String(time));
435 } 580 }
436 581
437 - return true;  
438 - } 582 + @Override
  583 + public void onStop(String filePath) {
  584 + i = (int) mAudioRecordUtil.getDuration();
  585 + mFilePath = filePath;
  586 + ChatUtil.sendVoiceMessage(IChat.CHATROOM, roomIdOne, Uri.parse(mFilePath), i, sendMessageListener);
  587 + }
  588 + };
439 589
440 - private void sendVoice() {  
441 - long l1 = System.currentTimeMillis();  
442 - long l2 = (l1 - l) / 1000;  
443 - i = (int) l2;  
444 - releaseRecord();  
445 - String recoderFile = getRecoderFile();  
446 - ChatUtil.sendVoiceMessage(IChat.CHATROOM, roomIdOne, Uri.parse(recoderFile), i, sendMessageListener);  
447 - } 590 + private TextWatcher textWatcher = new TextWatcher() {
  591 + @Override
  592 + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
  593 + }
448 594
449 - @Override  
450 - public void onItemClick(Uri uri) {  
451 - plaVoice(uri); 595 + @Override
  596 + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
452 597
453 - } 598 + }
  599 +
  600 + @Override
  601 + public void afterTextChanged(Editable editable) {
  602 + if (!TextUtils.isEmpty(editable)){
  603 + ima_send.setVisibility(View.GONE);
  604 + send_messageOneBtn.setVisibility(View.VISIBLE);
  605 + }else {
  606 + send_messageOneBtn.setVisibility(View.GONE);
  607 + ima_send.setVisibility(View.VISIBLE);
  608 + }
  609 + }
  610 + };
454 611
455 - public void plaVoice(Uri uri) {  
456 - mediaRecorderUtil.plaVoice(uri, this);  
457 - }  
458 } 612 }
app/src/main/java/com/cnlive/im/ui/PopupWindowFactory.java 0 → 100644
  1 +package com.cnlive.im.ui;
  2 +
  3 +import android.content.Context;
  4 +import android.graphics.drawable.BitmapDrawable;
  5 +import android.util.Log;
  6 +import android.view.KeyEvent;
  7 +import android.view.MotionEvent;
  8 +import android.view.View;
  9 +import android.view.ViewGroup;
  10 +import android.widget.PopupWindow;
  11 +
  12 +/**
  13 + * Created by Lynn on 2017/7/21.
  14 + */
  15 +
  16 +public class PopupWindowFactory {
  17 + private Context mContext;
  18 + private PopupWindow mPop;
  19 +
  20 + /**
  21 + * @param context 上下文
  22 + * @param view PopupWindow显示的布局文件
  23 + */
  24 + public PopupWindowFactory(Context context, View view) {
  25 + this(context, view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  26 + }
  27 +
  28 + /**
  29 + * @param context 上下文
  30 + * @param view PopupWindow显示的布局文件
  31 + * @param width PopupWindow的宽
  32 + * @param height PopupWindow的高
  33 + */
  34 + public PopupWindowFactory(Context context, View view, int width, int height) {
  35 + init(context, view, width, height);
  36 + }
  37 +
  38 +
  39 + private void init(Context context, View view, int width, int height) {
  40 + this.mContext = context;
  41 +
  42 + //下面这两个必须有
  43 + view.setFocusable(true);
  44 + view.setFocusableInTouchMode(true);
  45 +
  46 + //PopupWindow(布局,宽度、高度)
  47 + mPop = new PopupWindow(view, width, height, true);
  48 + mPop.setFocusable(true);
  49 + mPop.setOutsideTouchable(false);
  50 +
  51 + //重写onKeyListener,按返回键消失
  52 + view.setOnKeyListener(new View.OnKeyListener() {
  53 + @Override
  54 + public boolean onKey(View view, int i, KeyEvent keyEvent) {
  55 + if (i == KeyEvent.KEYCODE_BACK) {
  56 + mPop.dismiss();
  57 + return true;
  58 + }
  59 + return false;
  60 + }
  61 + });
  62 +
  63 +// 点击其他地方消失
  64 + view.setOnTouchListener(new View.OnTouchListener() {
  65 + @Override
  66 + public boolean onTouch(View view, MotionEvent motionEvent) {
  67 + if (mPop != null && mPop.isShowing()) {
  68 +// mPop.dismiss();
  69 + return true;
  70 + }
  71 + return false;
  72 + }
  73 + });
  74 +
  75 + }
  76 +
  77 + public PopupWindow getPopupWindow() {
  78 + return mPop;
  79 + }
  80 +
  81 + /**
  82 + * 以触发弹出窗的view为基准,出现在view的内部上面,弹出的pop_view左上角正对view的左上角
  83 + *
  84 + * @param parent view
  85 + * @param gravity 在view的什么位置Gravity.CENTER、Gravity.TOP......
  86 + * @param x 与控件的x坐标距离
  87 + * @param y 与控件的y坐标距离
  88 + */
  89 + public void showAtLocation(View parent, int gravity, int x, int y) {
  90 + if (mPop.isShowing()) {
  91 + return;
  92 + }
  93 + mPop.showAtLocation(parent, gravity, x, y);
  94 + }
  95 +
  96 + /**
  97 + * 以触发弹出窗的view为基准,出现在view的正下方,弹出的pop_view左上角正对view的左下角
  98 + *
  99 + * @param anchor view
  100 + */
  101 + public void showAsDropDown(View anchor) {
  102 + showAsDropDown(anchor, 0, 0);
  103 + }
  104 +
  105 + /**
  106 + * 以触发弹出窗的view为基准,出现在view的正下方,弹出的pop_view左上角正对view的左下角
  107 + *
  108 + * @param anchor view
  109 + * @param xoff 与view的x坐标距离
  110 + * @param yoff 与view的y坐标距离
  111 + */
  112 + public void showAsDropDown(View anchor, int xoff, int yoff) {
  113 + if (mPop.isShowing()) {
  114 + return;
  115 + }
  116 +
  117 + mPop.showAsDropDown(anchor, xoff, yoff);
  118 + }
  119 +
  120 + /**
  121 + * 隐藏popupwindow
  122 + */
  123 + public void dismiss() {
  124 + if (mPop.isShowing()) {
  125 + mPop.dismiss();
  126 + }
  127 + }
  128 +}
app/src/main/java/com/cnlive/im/ui/UserLoginActivity.java
@@ -50,7 +50,7 @@ public class UserLoginActivity extends AppCompatActivity implements View.OnClick @@ -50,7 +50,7 @@ public class UserLoginActivity extends AppCompatActivity implements View.OnClick
50 doIntentChatRoom(ChatRoomActivity.class); 50 doIntentChatRoom(ChatRoomActivity.class);
51 break; 51 break;
52 case R.id.private_chat: 52 case R.id.private_chat:
53 - // doIntentChatRoom(PrivateChatActivity.class); 53 + doIntentChatRoom(PrivateChatActivity.class);
54 54
55 break; 55 break;
56 } 56 }
app/src/main/java/com/cnlive/im/util/AudioRecordUtil.java 0 → 100644
  1 +package com.cnlive.im.util;
  2 +
  3 +import android.media.MediaRecorder;
  4 +import android.os.Environment;
  5 +import android.os.Handler;
  6 +import android.util.Log;
  7 +
  8 +import java.io.File;
  9 +import java.io.IOException;
  10 +import java.text.SimpleDateFormat;
  11 +
  12 +/**
  13 + * Created by Lynn on 2017/7/20.
  14 + */
  15 +
  16 +public class AudioRecordUtil {
  17 + //文件路径
  18 + private String filePath;
  19 + //文件夹路径
  20 + private String FolderPath;
  21 +
  22 + private MediaRecorder mMediaRecorder;
  23 + private final String TAG = "fan";
  24 + public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;
  25 +
  26 + private OnAudioStatusUpdateListener audioStatusUpdateListener;
  27 +
  28 + /**
  29 + * 文件存储默认sdcard/record
  30 + */
  31 + public AudioRecordUtil() {
  32 +
  33 + //默认保存路径为/sdcard/record/下
  34 + this(Environment.getExternalStorageDirectory() + "/record/");
  35 + }
  36 +
  37 + public AudioRecordUtil(String filePath) {
  38 +
  39 + File path = new File(filePath);
  40 + if (!path.exists())
  41 + path.mkdirs();
  42 +
  43 + this.FolderPath = filePath;
  44 + }
  45 +
  46 + private long startTime;
  47 + private long endTime;
  48 +
  49 +
  50 + /**
  51 + * 开始录音 使用amr格式
  52 + * 录音文件
  53 + *
  54 + * @return
  55 + */
  56 + public void startRecord() {
  57 + // 开始录音
  58 + /* ①Initial:实例化MediaRecorder对象 */
  59 + if (mMediaRecorder == null)
  60 + mMediaRecorder = new MediaRecorder();
  61 + try {
  62 + /* ②setAudioSource/setVedioSource */
  63 + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
  64 + /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
  65 + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
  66 + /*
  67 + * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
  68 + * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
  69 + */
  70 + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  71 +
  72 + filePath = FolderPath + getCurrentTime() + ".amr";
  73 + /* ③准备 */
  74 + mMediaRecorder.setOutputFile(filePath);
  75 + mMediaRecorder.setMaxDuration(MAX_LENGTH);
  76 + mMediaRecorder.prepare();
  77 + /* ④开始 */
  78 + mMediaRecorder.start();
  79 + // AudioRecord audioRecord.
  80 + /* 获取开始时间* */
  81 + startTime = System.currentTimeMillis();
  82 + updateMicStatus();
  83 + Log.e("fan", "startTime" + startTime);
  84 + } catch (IllegalStateException e) {
  85 + Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
  86 + } catch (IOException e) {
  87 + Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
  88 + }
  89 + }
  90 +
  91 + /**
  92 + * 停止录音
  93 + */
  94 + public void stopRecord() {
  95 + if (mMediaRecorder == null)
  96 + return;
  97 + endTime = System.currentTimeMillis();
  98 +
  99 + //有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈!
  100 + try {
  101 + mMediaRecorder.stop();
  102 + mMediaRecorder.reset();
  103 + mMediaRecorder.release();
  104 + mMediaRecorder = null;
  105 +
  106 + audioStatusUpdateListener.onStop(filePath);
  107 + filePath = "";
  108 +
  109 + } catch (RuntimeException e) {
  110 + mMediaRecorder.reset();
  111 + mMediaRecorder.release();
  112 + mMediaRecorder = null;
  113 +
  114 + File file = new File(filePath);
  115 + if (file.exists())
  116 + file.delete();
  117 +
  118 + filePath = "";
  119 +
  120 + }
  121 +// return endTime - startTime;
  122 + }
  123 +
  124 + public long getDuration(){
  125 + return (endTime - startTime) / 1000;
  126 + }
  127 +
  128 + /**
  129 + * 取消录音
  130 + */
  131 + public void cancelRecord() {
  132 +
  133 + try {
  134 +
  135 + mMediaRecorder.stop();
  136 + mMediaRecorder.reset();
  137 + mMediaRecorder.release();
  138 + mMediaRecorder = null;
  139 +
  140 + } catch (RuntimeException e) {
  141 + mMediaRecorder.reset();
  142 + mMediaRecorder.release();
  143 + mMediaRecorder = null;
  144 + }
  145 + File file = new File(filePath);
  146 + if (file.exists())
  147 + file.delete();
  148 +
  149 + filePath = "";
  150 +
  151 + }
  152 +
  153 + private final Handler mHandler = new Handler();
  154 + private Runnable mUpdateMicStatusTimer = new Runnable() {
  155 + public void run() {
  156 + updateMicStatus();
  157 + }
  158 + };
  159 +
  160 +
  161 + private int BASE = 1;
  162 + private int SPACE = 100;// 间隔取样时间
  163 +
  164 + public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
  165 + this.audioStatusUpdateListener = audioStatusUpdateListener;
  166 + }
  167 +
  168 + /**
  169 + * 更新麦克状态
  170 + */
  171 + private void updateMicStatus() {
  172 +
  173 + if (mMediaRecorder != null) {
  174 + double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
  175 + double db = 0;// 分贝
  176 + if (ratio > 1) {
  177 + db = 20 * Math.log10(ratio);
  178 + if (null != audioStatusUpdateListener) {
  179 + audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);
  180 + }
  181 + }
  182 + mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
  183 + }
  184 + }
  185 +
  186 + /**
  187 + * 删除本地录制的文件
  188 + * @param filePath
  189 + */
  190 + public void deleteRecordFile(String filePath) {
  191 + File file = new File(filePath);
  192 + if (file.exists())
  193 + file.delete();
  194 + }
  195 +
  196 + public interface OnAudioStatusUpdateListener {
  197 + /**
  198 + * 录音中...
  199 + *
  200 + * @param db 当前声音分贝
  201 + * @param time 录音时长
  202 + */
  203 + public void onUpdate(double db, long time);
  204 +
  205 + /**
  206 + * 停止录音
  207 + *
  208 + * @param filePath 保存路径
  209 + */
  210 + public void onStop(String filePath);
  211 + }
  212 +
  213 + private String getCurrentTime() {
  214 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
  215 + return sdf.format(System.currentTimeMillis());
  216 + }
  217 +}
app/src/main/java/com/cnlive/im/util/IVoiceManager.java 0 → 100644
  1 +package com.cnlive.im.util;
  2 +
  3 +import android.content.Context;
  4 +import android.net.Uri;
  5 +
  6 +/**
  7 + * Created by Lynn on 2017/7/21.
  8 + */
  9 +
  10 +public interface IVoiceManager {
  11 + boolean start(Uri uri);
  12 + boolean stop();
  13 + void onDestory();
  14 +}
app/src/main/java/com/cnlive/im/util/PlayerUtil.java 0 → 100644
  1 +package com.cnlive.im.util;
  2 +
  3 +import android.content.Context;
  4 +import android.media.MediaPlayer;
  5 +import android.net.Uri;
  6 +
  7 +/**
  8 + * Created by Lynn on 2017/7/21.
  9 + */
  10 +
  11 +public class PlayerUtil implements IVoiceManager{
  12 + private final String TAG = "PlayerUtil";
  13 + private String path;
  14 + private Context mContext;
  15 +
  16 + private MediaPlayer mediaPlayer;
  17 +
  18 + public PlayerUtil(Context context){
  19 + this.mContext = context;
  20 +// mediaPlayer = new MediaPlayer();
  21 +// mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
  22 +// @Override
  23 +// public void onCompletion(MediaPlayer mediaPlayer) {
  24 +//// mediaPlayer.stop();
  25 +// stop();
  26 +// }
  27 +// });
  28 + }
  29 +
  30 + @Override
  31 + public boolean start(Uri uri) {
  32 + try {
  33 + mediaPlayer = new MediaPlayer();
  34 + mediaPlayer.setOnCompletionListener(onCompletionListener);
  35 + mediaPlayer.setOnPreparedListener(onPreparedListener);
  36 + mediaPlayer.setDataSource(mContext, uri);
  37 + mediaPlayer.prepare();
  38 +// mediaPlayer.start();
  39 + }catch (Exception e){
  40 + e.printStackTrace();
  41 + }
  42 + return false;
  43 + }
  44 +
  45 + @Override
  46 + public boolean stop() {
  47 + mediaPlayer.stop();
  48 + mediaPlayer.release();
  49 + mediaPlayer = null;
  50 + return false;
  51 + }
  52 +
  53 + @Override
  54 + public void onDestory() {
  55 + mediaPlayer.release();
  56 + mediaPlayer = null;
  57 + }
  58 +
  59 + private MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() {
  60 + @Override
  61 + public void onCompletion(MediaPlayer mediaPlayer) {
  62 + stop();
  63 + }
  64 + };
  65 +
  66 + private MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() {
  67 + @Override
  68 + public void onPrepared(MediaPlayer mediaPlayer) {
  69 + mediaPlayer.start();
  70 + }
  71 + };
  72 +}
app/src/main/java/com/cnlive/im/util/TimeUtils.java 0 → 100644
  1 +package com.cnlive.im.util;
  2 +
  3 +
  4 +import java.text.SimpleDateFormat;
  5 +
  6 +/**
  7 + * Created by fan on 2016/6/23.
  8 + */
  9 +public class TimeUtils {
  10 +
  11 + //毫秒转秒
  12 + public static String long2String(long time){
  13 +
  14 + //毫秒转秒
  15 + int sec = (int) time / 1000 ;
  16 + int min = sec / 60 ; //分钟
  17 + sec = sec % 60 ; //秒
  18 + if(min < 10){ //分钟补0
  19 + if(sec < 10){ //秒补0
  20 + return "0"+min+":0"+sec;
  21 + }else{
  22 + return "0"+min+":"+sec;
  23 + }
  24 + }else{
  25 + if(sec < 10){ //秒补0
  26 + return min+":0"+sec;
  27 + }else{
  28 + return min+":"+sec;
  29 + }
  30 + }
  31 +
  32 + }
  33 +
  34 + /**
  35 + * 返回当前时间的格式为 yyyy-MM-dd HH:mm:ss
  36 + * @return
  37 + */
  38 + public static String getCurrentTime() {
  39 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
  40 + return sdf.format(System.currentTimeMillis());
  41 + }
  42 +
  43 +
  44 +}
app/src/main/res/drawable-hdpi/keybord.png 0 → 100644

1.07 KB

app/src/main/res/drawable-hdpi/pic.png 0 → 100644

1.09 KB

app/src/main/res/drawable-hdpi/record_bottom.png 0 → 100644

5.5 KB

app/src/main/res/drawable-hdpi/record_top.png 0 → 100644

6.13 KB

app/src/main/res/drawable-hdpi/send_cancel.png 0 → 100644

788 Bytes

app/src/main/res/drawable-hdpi/voice.png 0 → 100644

1.2 KB

app/src/main/res/drawable-hdpi/voice_bg_left.png 0 → 100644

618 Bytes

app/src/main/res/drawable-hdpi/voice_bg_right.png 0 → 100644

607 Bytes

app/src/main/res/drawable-hdpi/voice_msg_left.png deleted 100644 → 0

1.2 KB

app/src/main/res/drawable-hdpi/voice_msg_right.9.png deleted 100644 → 0

1.19 KB

app/src/main/res/drawable/record_microphone.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  3 + <item android:id="@android:id/background" android:drawable="@drawable/record_top" />
  4 + <item android:id="@android:id/progress" >
  5 + <clip android:drawable="@drawable/record_bottom" android:gravity="bottom" android:clipOrientation="vertical" />
  6 + </item>
  7 +</layer-list>
0 \ No newline at end of file 8 \ No newline at end of file
app/src/main/res/drawable/record_microphone_bj.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:shape="rectangle">
  4 +
  5 + <corners
  6 + android:radius="8dp" />
  7 +
  8 + <solid
  9 + android:color="@color/colorGray" />
  10 +
  11 +</shape>
0 \ No newline at end of file 12 \ No newline at end of file
app/src/main/res/drawable/voice_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +
  3 +<shape xmlns:android="http://schemas.android.com/apk/res/android">
  4 + <solid android:color="#ffffff" />
  5 + <corners
  6 + android:bottomLeftRadius="10dp"
  7 + android:bottomRightRadius="10dp"
  8 + android:topLeftRadius="10dp"
  9 + android:topRightRadius="10dp" />
  10 + <stroke
  11 + android:width="1dp"
  12 + android:color="#8a8a8a" />
  13 +</shape>
0 \ No newline at end of file 14 \ No newline at end of file
app/src/main/res/layout/activity_groupchat.xml
@@ -11,7 +11,8 @@ @@ -11,7 +11,8 @@
11 android:layout_alignParentLeft="true" 11 android:layout_alignParentLeft="true"
12 android:layout_alignParentStart="true" 12 android:layout_alignParentStart="true"
13 android:layout_alignParentTop="true" 13 android:layout_alignParentTop="true"
14 - android:visibility="visible"> 14 + android:visibility="visible"
  15 + android:clickable="true">
15 16
16 <LinearLayout 17 <LinearLayout
17 android:id="@+id/listVIewTop" 18 android:id="@+id/listVIewTop"
@@ -30,7 +31,7 @@ @@ -30,7 +31,7 @@
30 <android.support.v7.widget.RecyclerView 31 <android.support.v7.widget.RecyclerView
31 android:id="@+id/chat_list_viewOne" 32 android:id="@+id/chat_list_viewOne"
32 android:layout_width="fill_parent" 33 android:layout_width="fill_parent"
33 - android:layout_height="170dp" 34 + android:layout_height="200dp"
34 android:layout_above="@+id/edit_layout" 35 android:layout_above="@+id/edit_layout"
35 android:divider="@null" 36 android:divider="@null"
36 android:fastScrollEnabled="true" 37 android:fastScrollEnabled="true"
@@ -45,13 +46,39 @@ @@ -45,13 +46,39 @@
45 android:layout_alignParentBottom="true" 46 android:layout_alignParentBottom="true"
46 android:orientation="horizontal"> 47 android:orientation="horizontal">
47 48
  49 + <ImageView
  50 + android:id="@+id/keybord"
  51 + android:layout_width="0dp"
  52 + android:layout_height="wrap_content"
  53 + android:layout_gravity="center_vertical"
  54 + android:layout_weight="0.1"
  55 + android:src="@drawable/voice" />
  56 +
48 <EditText 57 <EditText
49 android:id="@+id/message_content" 58 android:id="@+id/message_content"
50 android:layout_width="0dp" 59 android:layout_width="0dp"
51 android:layout_height="wrap_content" 60 android:layout_height="wrap_content"
52 android:layout_gravity="center" 61 android:layout_gravity="center"
53 - android:layout_weight="0.7" /> 62 + android:layout_weight="0.6" />
54 63
  64 + <Button
  65 + android:id="@+id/send_voice"
  66 + android:layout_width="0dp"
  67 + android:layout_height="40dp"
  68 + android:layout_margin="10dp"
  69 + android:layout_weight="0.6"
  70 + android:background="@drawable/voice_bg"
  71 + android:text="按住说话"
  72 + android:visibility="gone" />
  73 +
  74 + <ImageView
  75 + android:id="@+id/ima_send"
  76 + android:layout_width="0dp"
  77 + android:layout_height="wrap_content"
  78 + android:layout_gravity="center_vertical"
  79 + android:layout_weight="0.1"
  80 + android:src="@drawable/pic"
  81 + android:visibility="visible"/>
55 82
56 <Button 83 <Button
57 android:id="@+id/send_messageOne" 84 android:id="@+id/send_messageOne"
@@ -59,7 +86,8 @@ @@ -59,7 +86,8 @@
59 android:layout_height="wrap_content" 86 android:layout_height="wrap_content"
60 android:layout_gravity="center" 87 android:layout_gravity="center"
61 android:layout_weight="0.2" 88 android:layout_weight="0.2"
62 - android:text="发送" /> 89 + android:text="发送"
  90 + android:visibility="gone"/>
63 91
64 </LinearLayout> 92 </LinearLayout>
65 93
@@ -80,18 +108,11 @@ @@ -80,18 +108,11 @@
80 android:layout_height="wrap_content" 108 android:layout_height="wrap_content"
81 android:text="加入聊天室" /> 109 android:text="加入聊天室" />
82 110
83 - <Button  
84 - android:id="@+id/ima_send"  
85 - android:layout_width="wrap_content"  
86 - android:layout_height="wrap_content"  
87 - android:text="图片" />  
88 -  
89 - <Button  
90 - android:id="@+id/voice_send"  
91 - android:layout_width="wrap_content"  
92 - android:layout_height="wrap_content"  
93 - android:text="语音" />  
94 - 111 + <!--<Button-->
  112 + <!--android:id="@+id/ima_send"-->
  113 + <!--android:layout_width="wrap_content"-->
  114 + <!--android:layout_height="wrap_content"-->
  115 + <!--android:text="图片" />-->
95 116
96 <Button 117 <Button
97 android:id="@+id/exit_chat1" 118 android:id="@+id/exit_chat1"
app/src/main/res/layout/imag_layout_item_oppsite.xml
@@ -2,7 +2,9 @@ @@ -2,7 +2,9 @@
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent" 3 android:layout_width="match_parent"
4 android:layout_height="match_parent" 4 android:layout_height="match_parent"
5 - android:orientation="vertical"> 5 + android:orientation="vertical"
  6 + android:paddingBottom="10dp"
  7 + android:paddingTop="10dp">
6 8
7 9
8 <RelativeLayout 10 <RelativeLayout
app/src/main/res/layout/imag_layout_item_user.xml
@@ -2,7 +2,9 @@ @@ -2,7 +2,9 @@
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent" 3 android:layout_width="match_parent"
4 android:layout_height="match_parent" 4 android:layout_height="match_parent"
5 - android:orientation="vertical"> 5 + android:orientation="vertical"
  6 + android:paddingBottom="10dp"
  7 + android:paddingTop="10dp">
6 8
7 <RelativeLayout 9 <RelativeLayout
8 android:layout_width="wrap_content" 10 android:layout_width="wrap_content"
app/src/main/res/layout/layout_microphone.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:orientation="vertical">
  6 +
  7 + <LinearLayout
  8 + android:id="@+id/send_layout"
  9 + android:layout_width="wrap_content"
  10 + android:layout_height="wrap_content"
  11 + android:background="@drawable/record_microphone_bj"
  12 + android:gravity="center"
  13 + android:orientation="vertical"
  14 + android:padding="16dp"
  15 + android:layout_centerInParent="true">
  16 +
  17 + <ImageView
  18 + android:id="@+id/iv_recording_icon"
  19 + android:layout_width="48dp"
  20 + android:layout_height="48dp"
  21 + android:src="@drawable/record_microphone" />
  22 +
  23 + <TextView
  24 + android:id="@+id/tv_recording_time"
  25 + android:layout_width="114dp"
  26 + android:layout_height="wrap_content"
  27 + android:layout_marginTop="6dp"
  28 + android:gravity="center"
  29 + android:text="00:00"
  30 + android:textColor="#FFFFFF"
  31 + android:textSize="16sp" />
  32 +
  33 + <TextView
  34 + android:layout_width="wrap_content"
  35 + android:layout_height="wrap_content"
  36 + android:text="上滑取消发送"
  37 + android:textColor="@android:color/white"
  38 + android:textSize="16sp" />
  39 + </LinearLayout>
  40 +
  41 + <LinearLayout
  42 + android:id="@+id/cancel_send_layout"
  43 + android:layout_width="wrap_content"
  44 + android:layout_height="wrap_content"
  45 + android:background="@drawable/record_microphone_bj"
  46 + android:gravity="center"
  47 + android:orientation="vertical"
  48 + android:padding="16dp"
  49 + android:visibility="gone"
  50 + android:layout_centerInParent="true">
  51 +
  52 + <ImageView
  53 + android:layout_width="48dp"
  54 + android:layout_height="48dp"
  55 + android:src="@drawable/send_cancel" />
  56 +
  57 + <TextView
  58 + android:layout_width="wrap_content"
  59 + android:layout_height="wrap_content"
  60 + android:text="松开手指,取消发送"
  61 + android:textColor="#ffffff"
  62 + android:textSize="16sp" />
  63 + </LinearLayout>
  64 +</RelativeLayout>
0 \ No newline at end of file 65 \ No newline at end of file
app/src/main/res/layout/string_layout_item_oppsite.xml
@@ -2,7 +2,9 @@ @@ -2,7 +2,9 @@
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="wrap_content" 3 android:layout_width="wrap_content"
4 android:layout_height="wrap_content" 4 android:layout_height="wrap_content"
5 - android:orientation="vertical"> 5 + android:orientation="vertical"
  6 + android:paddingTop="10dp"
  7 + android:paddingBottom="10dp">
6 8
7 <RelativeLayout 9 <RelativeLayout
8 android:id="@+id/re_chat_room" 10 android:id="@+id/re_chat_room"
@@ -25,7 +27,8 @@ @@ -25,7 +27,8 @@
25 android:layout_toRightOf="@id/user_name" 27 android:layout_toRightOf="@id/user_name"
26 android:text="vfgfgf" 28 android:text="vfgfgf"
27 android:textColor="@android:color/black" 29 android:textColor="@android:color/black"
28 - android:textSize="15sp" /> 30 + android:textSize="15sp"
  31 + android:layout_marginLeft="5dp"/>
29 </RelativeLayout> 32 </RelativeLayout>
30 33
31 </RelativeLayout> 34 </RelativeLayout>
32 \ No newline at end of file 35 \ No newline at end of file
app/src/main/res/layout/string_layout_item_user.xml
1 <?xml version="1.0" encoding="utf-8"?> 1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="wrap_content" 3 android:layout_width="wrap_content"
4 - android:layout_height="wrap_content"> 4 + android:layout_height="wrap_content"
  5 + android:paddingBottom="10dp"
  6 + android:paddingTop="10dp">
5 7
6 <RelativeLayout 8 <RelativeLayout
7 android:id="@+id/re_chat_room" 9 android:id="@+id/re_chat_room"
@@ -25,7 +27,8 @@ @@ -25,7 +27,8 @@
25 android:layout_toLeftOf="@id/user_name" 27 android:layout_toLeftOf="@id/user_name"
26 android:text="vfgfgf" 28 android:text="vfgfgf"
27 android:textColor="@android:color/black" 29 android:textColor="@android:color/black"
28 - android:textSize="15sp" /> 30 + android:textSize="15sp"
  31 + android:layout_marginRight="5dp"/>
29 </RelativeLayout> 32 </RelativeLayout>
30 33
31 34
app/src/main/res/layout/voice_layout_item_oppsite.xml
1 <?xml version="1.0" encoding="utf-8"?> 1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="wrap_content" 3 android:layout_width="wrap_content"
4 - android:layout_height="wrap_content"> 4 + android:layout_height="wrap_content"
  5 + android:paddingBottom="10dp"
  6 + android:paddingTop="10dp">
5 7
6 8
7 <RelativeLayout 9 <RelativeLayout
@@ -16,12 +18,20 @@ @@ -16,12 +18,20 @@
16 android:layout_centerVertical="true" 18 android:layout_centerVertical="true"
17 android:text="游客" /> 19 android:text="游客" />
18 20
19 - <ImageView 21 + <TextView
20 android:id="@+id/voice_content" 22 android:id="@+id/voice_content"
21 android:layout_width="wrap_content" 23 android:layout_width="wrap_content"
22 - android:layout_height="wrap_content" 24 + android:layout_height="50dp"
23 android:layout_toRightOf="@+id/voice_user_name" 25 android:layout_toRightOf="@+id/voice_user_name"
24 - android:src="@drawable/voice_msg_left" /> 26 + android:background="@drawable/voice_bg_left"
  27 + android:layout_marginLeft="5dp"/>
  28 +
  29 + <TextView
  30 + android:id="@+id/voice_duration"
  31 + android:layout_width="wrap_content"
  32 + android:layout_height="wrap_content"
  33 + android:layout_centerVertical="true"
  34 + android:layout_toRightOf="@id/voice_content" />
25 35
26 36
27 </RelativeLayout> 37 </RelativeLayout>
app/src/main/res/layout/voice_layout_item_user.xml
1 <?xml version="1.0" encoding="utf-8"?> 1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="wrap_content" 3 android:layout_width="wrap_content"
4 - android:layout_height="wrap_content"> 4 + android:layout_height="wrap_content"
  5 + android:paddingBottom="10dp"
  6 + android:paddingTop="10dp">
5 7
6 <RelativeLayout 8 <RelativeLayout
7 android:layout_width="wrap_content" 9 android:layout_width="wrap_content"
@@ -15,12 +17,20 @@ @@ -15,12 +17,20 @@
15 android:layout_centerVertical="true" 17 android:layout_centerVertical="true"
16 android:text="游客" /> 18 android:text="游客" />
17 19
18 - <ImageView 20 + <TextView
19 android:id="@+id/voice_content" 21 android:id="@+id/voice_content"
20 android:layout_width="wrap_content" 22 android:layout_width="wrap_content"
21 - android:layout_height="wrap_content" 23 + android:layout_height="50dp"
22 android:layout_toLeftOf="@+id/voice_user_name" 24 android:layout_toLeftOf="@+id/voice_user_name"
23 - android:src="@drawable/voice_msg_right" /> 25 + android:background="@drawable/voice_bg_right"
  26 + android:layout_marginRight="5dp"/>
  27 +
  28 + <TextView
  29 + android:id="@+id/voice_duration"
  30 + android:layout_width="wrap_content"
  31 + android:layout_height="wrap_content"
  32 + android:layout_centerVertical="true"
  33 + android:layout_toLeftOf="@id/voice_content" />
24 34
25 </RelativeLayout> 35 </RelativeLayout>
26 36
app/src/main/res/values/colors.xml
@@ -3,4 +3,5 @@ @@ -3,4 +3,5 @@
3 <color name="colorPrimary">#3F51B5</color> 3 <color name="colorPrimary">#3F51B5</color>
4 <color name="colorPrimaryDark">#303F9F</color> 4 <color name="colorPrimaryDark">#303F9F</color>
5 <color name="colorAccent">#FF4081</color> 5 <color name="colorAccent">#FF4081</color>
  6 + <color name="colorGray">#33000000</color>
6 </resources> 7 </resources>