diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..7ac24c7
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..1e1352c
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..3b31283
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d518b8c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\DwayneQ\AppData\Roaming\Subversion
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..88d7145
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..c29437e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,46 @@
+apply plugin: 'com.android.application'
+apply plugin: 'org.greenrobot.greendao' // apply plugin
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.3"
+ defaultConfig {
+ applicationId "com.example.administrator.chatdemo"
+ minSdkVersion 16
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:appcompat-v7:25.3.1'
+ compile 'com.android.support.constraint:constraint-layout:1.0.2'
+ compile 'com.android.support:recyclerview-v7:23.1.1'
+ testCompile 'junit:junit:4.12'
+ // greenDao
+ compile 'org.greenrobot:greendao:3.2.2'
+ compile 'com.github.LuckSiege.PictureSelector:picture_library:v2.1.0'
+
+
+ compile 'io.github.leibnik:chatimageview:1.0.1'
+ compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
+ compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
+
+}
+greendao {
+ schemaVersion 1
+// daoPackage 'com.fengpi.gen'
+// targetGenDir 'src/main/java'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..ce3149c
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..06ce7bd
--- /dev/null
+++ b/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.administrator.chatdemo;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.example.administrator.chatdemo", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..feb99a1
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/App.java b/app/src/main/java/com/example/administrator/chatdemo/App.java
new file mode 100644
index 0000000..626aa5e
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/App.java
@@ -0,0 +1,55 @@
+package com.example.administrator.chatdemo;
+
+import android.app.Application;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.example.administrator.chatdemo.bean.DaoMaster;
+import com.example.administrator.chatdemo.bean.DaoSession;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
+import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
+
+/**
+ * Created by dwq on 2017/7/20/020.
+ * e-mail:lomapa@163.com
+ */
+
+public class App extends Application {
+
+ private static App instance;
+
+ public DaoSession getDaoSession() {
+ return daoSession;
+ }
+
+ private DaoSession daoSession;
+
+ public static App getInstance() {
+ return instance;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ DaoMaster.DevOpenHelper mHelper = new DaoMaster.DevOpenHelper(this, "chat-message", null);
+ SQLiteDatabase db = mHelper.getWritableDatabase();
+ daoSession = new DaoMaster(db).newSession();
+ initImageLoader(this);
+ instance = this;
+ }
+
+ /**
+ * 初始化ImageLoader
+ */
+ public static void initImageLoader(Context context) {
+ ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
+ context)
+ .threadPoolSize(3).threadPriority(Thread.NORM_PRIORITY - 2)
+ //.memoryCache(new WeakMemoryCache())
+ .denyCacheImageMultipleSizesInMemory()
+ .tasksProcessingOrder(QueueProcessingType.LIFO)
+ .build();
+ ImageLoader.getInstance().init(config);
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java b/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java
new file mode 100644
index 0000000..04031ec
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java
@@ -0,0 +1,92 @@
+package com.example.administrator.chatdemo;
+
+import android.media.MediaPlayer;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Created by lzw on 14/12/19.
+ */
+public class AudioHelper {
+ private static AudioHelper audioHelper;
+ private MediaPlayer mediaPlayer;
+ private Runnable finishCallback;
+ private String audioPath;
+ private boolean onceStart = false;
+ private boolean isPlaying = false;
+
+ private AudioHelper() {
+
+ }
+
+ public static AudioHelper getInstance() {
+ if (audioHelper == null) {
+ audioHelper = new AudioHelper();
+ }
+ return audioHelper;
+ }
+
+ public String getAudioPath() {
+ return audioPath;
+ }
+
+ public void stopPlayer() {
+ if (mediaPlayer != null) {
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ }
+ isPlaying = false;
+ mediaPlayer = null;
+ }
+
+ public void pausePlayer() {
+ if (mediaPlayer != null) {
+ mediaPlayer.pause();
+ isPlaying = false;
+ }
+ }
+
+ public boolean isPlaying() {
+ return isPlaying;
+ }
+
+ public void restartPlayer() {
+ if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
+ mediaPlayer.start();
+ }
+ }
+
+ public synchronized void playAudio(String path, Runnable finishCallback) {
+ if (mediaPlayer != null && onceStart) {
+ mediaPlayer.reset();
+ } else {
+ mediaPlayer = new MediaPlayer();
+ }
+// tryRunFinishCallback();
+ audioPath = path;
+ AudioHelper.this.finishCallback = finishCallback;
+ try {
+ mediaPlayer.setDataSource(path);
+ mediaPlayer.prepare();
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ tryRunFinishCallback();
+ }
+ });
+ isPlaying = true;
+ mediaPlayer.start();
+ onceStart = true;
+ } catch (IOException e) {
+ Log.e("AudioHelper", e.toString());
+ }
+ }
+
+ public void tryRunFinishCallback() {
+ if (finishCallback != null) {
+ finishCallback.run();
+ finishCallback = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java b/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java
new file mode 100644
index 0000000..99334ad
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java
@@ -0,0 +1,38 @@
+package com.example.administrator.chatdemo;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.example.administrator.chatdemo.utils.Constants;
+import com.example.administrator.chatdemo.utils.PhotoUtils;
+
+
+/**
+ * Created by lzw on 14-9-21.
+ */
+public class ImageBrowserActivity extends Activity {
+
+ private ImageView imageView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.chat_image_brower_layout);
+ imageView = (ImageView) findViewById(R.id.imageView);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ Intent intent = getIntent();
+ String path = intent.getStringExtra(Constants.IMAGE_LOCAL_PATH);
+ String url = intent.getStringExtra(Constants.IMAGE_URL);
+ PhotoUtils.displayImageCacheElseNetwork(imageView, path, url);
+ findViewById(R.id.lly_image_browser).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java b/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java
new file mode 100644
index 0000000..4b21051
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java
@@ -0,0 +1,102 @@
+package com.example.administrator.chatdemo;
+
+import android.content.Context;
+import android.graphics.drawable.AnimationDrawable;
+import android.media.MediaPlayer;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.widget.PlayButton;
+
+import java.io.File;
+
+/**
+ * Created by dwq on 2017/7/24/024.
+ * e-mail:lomapa@163.com
+ */
+
+public class PlayButtonClickListener implements View.OnClickListener {
+ private PlayButton playButton;
+ private boolean leftSide;
+ private Context context;
+ private String path;
+
+ private AnimationDrawable anim;
+ public static PlayButtonClickListener mCurrentPlayButtonClickListner = null;
+ private MediaPlayer mediaPlayer = null;
+ public static boolean isPlaying = false;
+ // 将MediaPlayer播放地址,
+ public static String audioPath;
+ private ImageView ivIsListened;
+ private ChatMessageBean chatMessageBean;
+
+ public PlayButtonClickListener(PlayButton playButton, ImageView ivIsListened, ChatMessageBean chatMessageBean, boolean leftSide, Context context, String path) {
+ this.playButton = playButton;
+ this.leftSide = leftSide;
+ this.context = context;
+ this.path = path;
+ this.ivIsListened = ivIsListened;
+ this.chatMessageBean = chatMessageBean;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (isPlaying) {
+ if (TextUtils.equals(audioPath, path)) {
+ mCurrentPlayButtonClickListner.stopPlayVoice();
+ return;
+ }
+ mCurrentPlayButtonClickListner.stopPlayVoice();
+ }
+ playButton.startRecordAnimation();
+ playVoice(path);
+ if (leftSide && ivIsListened != null) {
+ ivIsListened.setVisibility(View.INVISIBLE);
+ chatMessageBean.setIsListened(true);
+// context
+ App.getInstance().getDaoSession().update(chatMessageBean);
+ }
+ }
+
+ public void stopPlayVoice() {
+ playButton.stopRecordAnimation();
+ if (mediaPlayer != null) {
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ }
+ isPlaying = false;
+ }
+
+ public void playVoice(String filePath) {
+ if (!(new File(filePath).exists())) {
+ return;
+ }
+ mediaPlayer = new MediaPlayer();
+ this.audioPath = path;
+ try {
+ mediaPlayer.setDataSource(filePath);
+ mediaPlayer.prepare();
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ // TODO Auto-generated method stub
+ mediaPlayer.release();
+ mediaPlayer = null;
+ stopPlayVoice(); // stop animation
+ }
+
+ });
+ isPlaying = true;
+ mCurrentPlayButtonClickListner = this;
+ mediaPlayer.start();
+ playButton.startRecordAnimation();
+ } catch (Exception e) {
+ System.out.println();
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java b/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java
new file mode 100644
index 0000000..0784f70
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java
@@ -0,0 +1,482 @@
+package com.example.administrator.chatdemo;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Toast;
+
+import com.example.administrator.chatdemo.adapter.ChatRecyclerAdapter;
+import com.example.administrator.chatdemo.bean.ChatConst;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.bean.ChatMessageType;
+import com.example.administrator.chatdemo.bean.DaoSession;
+import com.example.administrator.chatdemo.utils.ImageCheckoutUtil;
+import com.example.administrator.chatdemo.utils.PathUtils;
+import com.example.administrator.chatdemo.widget.ChatBottomView;
+import com.example.administrator.chatdemo.widget.InputBarLayout;
+import com.luck.picture.lib.PictureSelector;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ServiceChatActivity extends AppCompatActivity {
+
+ private View activityRootView;
+
+ private static final int SDK_PERMISSION_REQUEST = 127;
+ private static final int IMAGE_SIZE = 100 * 1024;// 300kb
+ private String permissionInfo;
+ private boolean CAN_WRITE_EXTERNAL_STORAGE = true;
+ private boolean CAN_RECORD_AUDIO = true;
+ private String camPicPath;
+ private InputBarLayout inputbarLayout;
+ private RecyclerView rvChat;
+ private LinearLayoutManager layoutManager;
+
+ public List messageList = new ArrayList<>();
+ private ChatRecyclerAdapter chatRecyclerAdapter;
+
+ public String userName = "Dwq";//聊天对象昵称
+
+ public static final int SEND_OK = 0x1110;
+ public static final int REFRESH = 0x0011;
+ public static final int RECERIVE_OK = 0x1111;
+ public static final int PULL_TO_REFRESH_DOWN = 0x0111;
+ private SendMessageHandler sendMessageHandler;
+ private String content;
+ private DaoSession daoSession;
+ private String voiceFilePath;
+ private SwipeRefreshLayout swipeRefresh;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_service_chat);
+ initView();
+ initListener();
+ initData();
+ getPersimmions();
+ }
+
+
+ private void initView() {
+ activityRootView = findViewById(R.id.layout_tongbao_rl);
+ inputbarLayout = (InputBarLayout) findViewById(R.id.input_bar);
+ swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
+ rvChat = (RecyclerView) findViewById(R.id.rv_chat);
+ layoutManager = new LinearLayoutManager(this);
+ rvChat.setLayoutManager(layoutManager);
+ }
+
+ private void initListener() {
+ inputbarLayout.setOnBottomIconClickListener(new InputBarLayout.OnBottomIconClickListener() {
+ @Override
+ public void onIconClickListener(int from) {
+ switch (from) {
+ case ChatBottomView.FROM_CAMERA:// 相机
+ if (!CAN_WRITE_EXTERNAL_STORAGE) {
+ Toast.makeText(ServiceChatActivity.this, "权限未开通\n请到设置中开通相册权限", Toast.LENGTH_SHORT).show();
+ } else {
+ camPicPath = PathUtils.getSavePicPath(ServiceChatActivity.this);
+ Intent openCameraIntent = new Intent(
+ MediaStore.ACTION_IMAGE_CAPTURE);
+ Uri uri = Uri.fromFile(new File(camPicPath));
+ openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ startActivityForResult(openCameraIntent,
+ ChatBottomView.FROM_CAMERA);
+ }
+ break;
+ case ChatBottomView.FROM_GALLERY:// 相册
+ PictureSelector.create(ServiceChatActivity.this)
+ .openGallery(PictureMimeType.ofImage())
+ .imageSpanCount(4)// 每行显示个数
+ .selectionMode(PictureConfig.SINGLE)
+ .previewImage(true)
+ .compress(true)
+ .isCamera(false)
+ .forResult(PictureConfig.CHOOSE_REQUEST);
+ break;
+ }
+ }
+ });
+
+ inputbarLayout.setOnMessageSendListener(new InputBarLayout.OnMessageSendListener() {
+ @Override
+ public void sendMessage(final String msg) {// 发送文字消息
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+// messageList.add(getTbub(userName, ChatMessageType.TextMessageType.getType(), msg, null, null,
+// null, null, null, 0f, ChatConst.COMPLETED));
+ chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.TextMessageType.getType(),
+ msg, null, null, null, null, null, 0f, ChatConst.COMPLETED));
+ sendMessageHandler.sendEmptyMessage(SEND_OK);
+ ServiceChatActivity.this.content = msg;
+ receriveHandler.sendEmptyMessageDelayed(0, 1000);
+ }
+ }).start();
+ }
+
+ @Override
+ public void sendVoice(float seconds, String voicePath) {// 发送语音消息
+ sendAudio(seconds, voicePath);
+ }
+
+ @Override
+ public void onVoiceRecordStart() {
+ chatRecyclerAdapter.pausePlayer();
+ }
+ });
+
+ rvChat.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ inputbarLayout.hideBottomLayout();
+ return false;
+ }
+ });
+ }
+
+ private void initData() {
+
+ daoSession = ((App) getApplication()).getDaoSession();
+ loadLocalMessage();
+ chatRecyclerAdapter = new ChatRecyclerAdapter(this, messageList);
+ chatRecyclerAdapter.resetRecycledViewPoolSize(rvChat);
+
+ rvChat.setAdapter(chatRecyclerAdapter);
+ rvChat.scrollToPosition(messageList.size() - 1);
+ sendMessageHandler = new SendMessageHandler(this);
+
+ }
+
+ private void loadLocalMessage() {
+ if (messageList != null) {
+ messageList.clear();
+ }
+ List chatMessageBeen = daoSession.loadAll(ChatMessageBean.class);
+ messageList.addAll(chatMessageBeen);
+ }
+
+
+ @TargetApi(23)
+ protected void getPersimmions() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ ArrayList permissions = new ArrayList();
+ // 读写权限
+ if (addPermission(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n";
+ }
+ // 麦克风权限
+ if (addPermission(permissions, Manifest.permission.RECORD_AUDIO)) {
+ permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n";
+ }
+ if (permissions.size() > 0) {
+ requestPermissions(permissions.toArray(new String[permissions.size()]), SDK_PERMISSION_REQUEST);
+ }
+ }
+ }
+
+ @TargetApi(23)
+ private boolean addPermission(ArrayList permissionsList, String permission) {
+ if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { // 如果应用没有获得对应权限,则添加到列表中,准备批量申请
+ if (shouldShowRequestPermissionRationale(permission)) {
+ return true;
+ } else {
+ permissionsList.add(permission);
+ return false;
+ }
+
+ } else {
+ return true;
+ }
+ }
+
+ @TargetApi(23)
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ // TODO Auto-generated method stub
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case SDK_PERMISSION_REQUEST:
+ Map perms = new HashMap();
+ // Initial
+ perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED);
+ perms.put(Manifest.permission.RECORD_AUDIO, PackageManager.PERMISSION_GRANTED);
+ // Fill with results
+ for (int i = 0; i < permissions.length; i++)
+ perms.put(permissions[i], grantResults[i]);
+ // Check for ACCESS_FINE_LOCATION
+ if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ // Permission Denied
+ CAN_WRITE_EXTERNAL_STORAGE = false;
+ Toast.makeText(this, "禁用图片权限将导致发送图片功能无法使用!", Toast.LENGTH_SHORT)
+ .show();
+ }
+ if (perms.get(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ CAN_RECORD_AUDIO = false;
+ Toast.makeText(this, "禁用录制音频权限将导致语音功能无法使用!", Toast.LENGTH_SHORT)
+ .show();
+ }
+ break;
+ default:
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case ChatBottomView.FROM_CAMERA:
+ FileInputStream is = null;
+ try {
+ is = new FileInputStream(camPicPath);
+ File camFile = new File(camPicPath); // 图片文件路径
+ if (camFile.exists()) {
+ int size = ImageCheckoutUtil
+ .getImageSize(ImageCheckoutUtil
+ .getLoacalBitmap(camPicPath));
+// if (size > IMAGE_SIZE) {
+// showDialog(camPicPath);
+// Toast.makeText(this, "ImgPath=" + camPicPath, Toast.LENGTH_SHORT).show();
+// } else {
+ sendImage(camPicPath);
+// Toast.makeText(this, "ImgPath=" + camPicPath, Toast.LENGTH_SHORT).show();
+// }
+ } else {
+ Toast.makeText(this, "该文件不存在", Toast.LENGTH_SHORT).show();
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ // 关闭流
+ try {
+ is.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ break;
+ case PictureConfig.CHOOSE_REQUEST:
+ // 图片选择结果回调
+ List selectList = PictureSelector.obtainMultipleResult(data);
+ LocalMedia media = selectList.get(0);
+ String path = media.getPath();
+ sendImage(path);
+ // 例如 LocalMedia 里面返回三种path
+ // 1.media.getPath(); 为原图path
+ // 2.media.getCutPath();为裁剪后path,需判断media.isCut();是否为true
+ // 3.media.getCompressPath();为压缩后path,需判断media.isCompressed();是否为true
+ // 如果裁剪并压缩了,已取压缩路径为准,因为是先裁剪后压缩的
+// mDesignCenterView.refreshSelectedPicture(selectList);
+ break;
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+// Toast.makeText(this, "操作取消", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public ChatMessageBean getTbub(String username, int type, int messageType,
+ String Content, String imageIconUrl, String imageUrl,
+ String imageLocal, String userVoicePath, String userVoiceUrl,
+ Float userVoiceTime, @ChatConst.SendState int sendState) {
+ ChatMessageBean tbub = new ChatMessageBean();
+ tbub.setUserName(username);
+ long time = System.currentTimeMillis();
+ Log.i("serviceChat", "currentTime:" + time);
+ tbub.setTime(time);
+ tbub.setType(type);
+ tbub.setMessagetype(messageType);
+ tbub.setUserContent(Content);
+ tbub.setImageIconUrl(imageIconUrl);
+ tbub.setImageUrl(imageUrl);
+ tbub.setUserVoicePath(userVoicePath);
+ tbub.setUserVoiceUrl(userVoiceUrl);
+ tbub.setUserVoiceTime(userVoiceTime);
+ tbub.setSendState(sendState);
+ tbub.setImageLocal(imageLocal);
+ daoSession.insert(tbub);
+
+ return tbub;
+ }
+
+ private String filePath = "";
+
+ protected void sendImage(final String filePath) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.ImageMessageType.getType(), null, null, null, filePath, null, null,
+ 0f, ChatConst.COMPLETED));
+// imageList.add(tblist.get(tblist.size() - 1).getImageLocal());
+// imagePosition.put(tblist.size() - 1, imageList.size() - 1);
+ sendMessageHandler.sendEmptyMessage(SEND_OK);
+ ServiceChatActivity.this.filePath = filePath;
+ receriveHandler.sendEmptyMessageDelayed(1, 3000);
+ }
+ }).start();
+ }
+
+
+ /**
+ * 发送语音
+ */
+ protected void sendAudio(final float seconds, final String filePath) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.AudioMessageType.getType(), null, null, null, null, filePath,
+ null, seconds, ChatConst.COMPLETED));
+ sendMessageHandler.sendEmptyMessage(SEND_OK);
+ ServiceChatActivity.this.seconds = seconds;
+ voiceFilePath = filePath;
+ receriveHandler.sendEmptyMessageDelayed(2, 3000);
+ }
+ }).start();
+ }
+
+
+ /**
+ * 为了模拟接收延迟
+ */
+ private Handler receriveHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ switch (msg.what) {
+ case 0:
+ receriveMsgText(content);
+ break;
+// case 1:
+// receriveImageText(filePath);
+// break;
+ case 2:
+ receriveVoiceText(seconds, voiceFilePath);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private void receriveMsgText(final String content) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ String message = "回复:" + content;
+ ChatMessageBean tbub = new ChatMessageBean();
+ tbub.setUserName(userName);
+ long time = System.currentTimeMillis();
+ tbub.setUserContent(message);
+ tbub.setMessagetype(ChatMessageType.TextMessageType.getType());
+ tbub.setTime(time);
+ tbub.setType(1);
+ tbub.setSendState(ChatConst.COMPLETED);
+ chatRecyclerAdapter.addMessage(tbub);
+ sendMessageHandler.sendEmptyMessage(RECERIVE_OK);
+ daoSession.insert(tbub);
+ }
+ }).start();
+ }
+
+ /**
+ * 接收语音
+ */
+ float seconds = 0.0f;
+
+ private void receriveVoiceText(final float seconds, final String filePath) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ChatMessageBean tbub = new ChatMessageBean();
+ tbub.setUserName(userName);
+ long time = System.currentTimeMillis();
+ tbub.setTime(time);
+ tbub.setUserVoiceTime(seconds);
+ tbub.setUserVoicePath(filePath);
+ tbub.setType(1);
+ tbub.setMessagetype(ChatMessageType.AudioMessageType.getType());
+ tbub.setSendState(ChatConst.COMPLETED);
+ chatRecyclerAdapter.addMessage(tbub);
+ sendMessageHandler.sendEmptyMessage(RECERIVE_OK);
+ daoSession.insert(tbub);
+ }
+ }).start();
+ }
+
+ static class SendMessageHandler extends Handler {
+ WeakReference mActivity;
+
+ SendMessageHandler(ServiceChatActivity activity) {
+ mActivity = new WeakReference(activity);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO Auto-generated method stub
+ ServiceChatActivity theActivity = mActivity.get();
+ if (theActivity != null) {
+ switch (msg.what) {
+// case REFRESH:
+// theActivity.tbAdapter.isPicRefresh = true;
+// theActivity.tbAdapter.notifyDataSetChanged();
+// int position = theActivity.tbAdapter.getItemCount() - 1 < 0 ? 0 : theActivity.tbAdapter.getItemCount() - 1;
+// theActivity.myList.smoothScrollToPosition(position);
+// break;
+ case SEND_OK:
+// theActivity.chatRecyclerAdapter.isPicRefresh = true;
+// theActivity.chatRecyclerAdapter.addMessage;
+ theActivity.chatRecyclerAdapter.notifyItemInserted(theActivity.messageList
+ .size() - 1);
+ theActivity.rvChat.smoothScrollToPosition(theActivity.chatRecyclerAdapter.getItemCount() - 1);
+ break;
+ case RECERIVE_OK:
+// theActivity.chatRecyclerAdapter.isPicRefresh = true;
+ theActivity.chatRecyclerAdapter.notifyItemInserted(theActivity.messageList
+ .size() - 1);
+ theActivity.rvChat.smoothScrollToPosition(theActivity.chatRecyclerAdapter.getItemCount() - 1);
+ break;
+// case PULL_TO_REFRESH_DOWN:
+// theActivity.pullList.refreshComplete();
+// theActivity.tbAdapter.notifyDataSetChanged();
+// theActivity.rvChat.smoothScrollToPosition(theActivity.position - 1);
+// theActivity.isDown = false;
+// break;
+ default:
+ break;
+ }
+ }
+ }
+
+ }
+}
+
diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java
new file mode 100644
index 0000000..fee615d
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java
@@ -0,0 +1,59 @@
+package com.example.administrator.chatdemo.adapter;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.utils.EmotionHelper;
+import com.example.administrator.chatdemo.viewholder.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by lzw on 14-9-25.
+ */
+public class ChatEmotionGridAdapter extends BaseAdapter {
+ private Context context;
+ private List datas = new ArrayList<>();
+
+ public ChatEmotionGridAdapter(Context ctx) {
+ this.context = ctx;
+ }
+
+ public void setDatas(List datas) {
+ this.datas = datas;
+ }
+
+ @Override
+ public int getCount() {
+ return datas.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return datas.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public View getView(int position, View conView, ViewGroup parent) {
+ if (conView == null) {
+ conView = View.inflate(context, R.layout.chat_emotion_item, null);
+ }
+ ImageView emotionImageView = ViewHolder.findViewById(conView, R.id.emotionImageView);
+ String emotion = (String) getItem(position);
+ emotion = emotion.substring(1, emotion.length() - 1);
+ Bitmap bitmap = EmotionHelper.getEmojiDrawable(context, emotion);
+ emotionImageView.setImageBitmap(bitmap);
+ return conView;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java
new file mode 100644
index 0000000..1d71d96
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java
@@ -0,0 +1,41 @@
+package com.example.administrator.chatdemo.adapter;
+
+import android.support.v4.view.PagerAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by lzw on 14-9-25.
+ */
+public class ChatEmotionPagerAdapter extends PagerAdapter {
+
+ private List views = new ArrayList();
+
+ public ChatEmotionPagerAdapter(List views) {
+ this.views = views;
+ }
+
+ @Override
+ public int getCount() {
+ return views.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object o) {
+ return view == o;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ container.addView(views.get(position));
+ return views.get(position);
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ container.removeView(views.get(position));
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java
new file mode 100644
index 0000000..c4f4f77
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java
@@ -0,0 +1,174 @@
+package com.example.administrator.chatdemo.adapter;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.example.administrator.chatdemo.PlayButtonClickListener;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.bean.ChatMessageType;
+import com.example.administrator.chatdemo.viewholder.ChatItemAudioHolder;
+import com.example.administrator.chatdemo.viewholder.ChatItemHolder;
+import com.example.administrator.chatdemo.viewholder.ChatItemImageHolder;
+import com.example.administrator.chatdemo.viewholder.ChatItemTextHolder;
+import com.example.administrator.chatdemo.viewholder.CommonViewHolder;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by dwq on 2017/7/20/020.
+ * e-mail:lomapa@163.com
+ */
+
+public class ChatRecyclerAdapter extends RecyclerView.Adapter {
+
+ private final int ITEM_LEFT = 100;
+ private final int ITEM_LEFT_TEXT = 101;// 左侧文字
+ private final int ITEM_LEFT_IMAGE = 102;// 左侧图片
+ private final int ITEM_LEFT_AUDIO = 103;// 左侧语音
+
+ private final int ITEM_RIGHT = 200;
+ private final int ITEM_RIGHT_TEXT = 201;// 右侧文字
+ private final int ITEM_RIGHT_IMAGE = 202;// 右侧图片
+ private final int ITEM_RIGHT_AUDIO = 203;// 右侧语音
+
+ // 时间间隔最小为十分钟
+ private final static long TIME_INTERVAL = 1000 * 60 * 3;
+ private boolean isShowUserName = false;
+
+ private List chatMessageList = new ArrayList<>();
+ private Context context;
+ private int playButtonId = -1;
+ private boolean isMe;
+
+ public ChatRecyclerAdapter(Context context, List chatMessageList) {
+ this.context = context;
+ this.chatMessageList = chatMessageList;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ Log.i("MsgFrom", "viewType=" + viewType);
+ switch (viewType) {
+ case ITEM_LEFT_TEXT:
+ return new ChatItemTextHolder(parent.getContext(), parent, true);
+ case ITEM_LEFT_AUDIO:
+ return new ChatItemAudioHolder(parent.getContext(), parent, true);
+ case ITEM_LEFT_IMAGE:
+ return new ChatItemImageHolder(parent.getContext(), parent, true);
+ case ITEM_RIGHT_TEXT:
+ return new ChatItemTextHolder(parent.getContext(), parent, false);
+ case ITEM_RIGHT_AUDIO:
+ return new ChatItemAudioHolder(parent.getContext(), parent, false);
+ case ITEM_RIGHT_IMAGE:
+ return new ChatItemImageHolder(parent.getContext(), parent, false);
+ default:
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ChatMessageBean chatMessageBean = chatMessageList.get(position);
+ if (null != chatMessageBean) {
+ isMe = fromMe(chatMessageBean);
+ int messagetype = chatMessageBean.getMessagetype();
+ if (messagetype == ChatMessageType.TextMessageType.getType()) {
+ return isMe ? ITEM_LEFT_TEXT : ITEM_RIGHT_TEXT;
+ } else if (messagetype == ChatMessageType.ImageMessageType.getType()) {
+ return isMe ? ITEM_LEFT_IMAGE : ITEM_RIGHT_IMAGE;
+ } else if (messagetype == ChatMessageType.AudioMessageType.getType()) {
+ return isMe ? ITEM_LEFT_AUDIO : ITEM_RIGHT_AUDIO;
+ } else {
+ return isMe ? ITEM_LEFT : ITEM_RIGHT;
+ }
+
+ }
+ return 8888;
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ ((CommonViewHolder) holder).bindData(chatMessageList.get(position));
+ if (holder instanceof ChatItemHolder) {
+ ((ChatItemHolder) holder).showTimeView(shouldShowTime(position));
+ ((ChatItemHolder) holder).showUserName(isShowUserName);
+ }
+ if (getItemViewType(position) == ITEM_LEFT_AUDIO || getItemViewType(position) == ITEM_RIGHT_AUDIO) {
+ playButtonId = ((ChatItemAudioHolder) holder).playButton.getId();
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return chatMessageList.size();
+ }
+
+ public void setChatMessageList(List chatMessageList) {
+ this.chatMessageList.clear();
+ if (null != chatMessageList) {
+ this.chatMessageList.addAll(chatMessageList);
+ }
+ }
+
+ public void addMessageListAll(List chatMessageList) {
+ this.chatMessageList.addAll(0, chatMessageList);
+ }
+
+ public void addMessage(ChatMessageBean message) {
+ this.chatMessageList.addAll(Collections.singletonList(message));
+ }
+
+ public ChatMessageBean getFirstMessage() {
+ if (null != chatMessageList && chatMessageList.size() > 0) {
+ return this.chatMessageList.get(0);
+ } else {
+ return null;
+ }
+ }
+
+
+ private boolean fromMe(ChatMessageBean chatMessageBean) {
+ int type = chatMessageBean.getType();
+ if (type == 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private boolean shouldShowTime(int position) {
+ if (position == 0) {
+ return true;
+ }
+ long lastTime = chatMessageList.get(position - 1).getTime();
+ long curTime = chatMessageList.get(position).getTime();
+ return curTime - lastTime > TIME_INTERVAL;
+ }
+
+
+ /**
+ * 因为 RecyclerView 中的 item 缓存默认最大为 5,造成会重复的 create item 而卡顿
+ * 所以这里根据不同的类型设置不同的缓存值,经验值,不同 app 可以根据自己的场景进行更改
+ */
+ public void resetRecycledViewPoolSize(RecyclerView recyclerView) {
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_TEXT, 25);
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_IMAGE, 10);
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_AUDIO, 15);
+
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_TEXT, 25);
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_IMAGE, 10);
+ recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_AUDIO, 15);
+ }
+
+ public void pausePlayer() {
+ if (PlayButtonClickListener.isPlaying)
+ PlayButtonClickListener.mCurrentPlayButtonClickListner.stopPlayVoice();
+// }
+
+ }
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java
new file mode 100644
index 0000000..b4b426d
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java
@@ -0,0 +1,63 @@
+package com.example.administrator.chatdemo.adapter;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+
+
+public class DataAdapter extends BaseAdapter {
+
+ private String[] title;
+ private Context context;
+ public DataAdapter(Context context, String[] title) {
+ super();
+ this.context = context;
+ this.title = title;
+ }
+
+ @Override
+ public int getCount() {
+ return title.length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return title[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @SuppressLint("InflateParams")
+ @Override
+ public View getView(final int position, View convertView,
+ ViewGroup parent) {
+ ViewHolder holder;
+ if (convertView == null) {
+ holder = new ViewHolder();
+ convertView = LayoutInflater.from(context)
+ .inflate(R.layout.layout_mess_iv_listitem, null);
+ holder.money_Tv = (TextView) convertView
+ .findViewById(R.id.title);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.money_Tv.setText(title[position]);
+ return convertView;
+ }
+
+ class ViewHolder {
+
+ TextView money_Tv;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java
new file mode 100644
index 0000000..a857fd3
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java
@@ -0,0 +1,23 @@
+package com.example.administrator.chatdemo.bean;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by Mao Jiqing on 2016/10/15.
+ */
+
+public class ChatConst {
+ public static final String LISTVIEW_DATABASE_NAME = "listview.db";
+ public static final String RECYCLER_DATABASE_NAME = "recycler.db";
+ public static final int SENDING = 0;
+ public static final int COMPLETED = 1;
+ public static final int SENDERROR = 2;
+
+ @IntDef({SENDING, COMPLETED, SENDERROR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SendState {
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java
new file mode 100644
index 0000000..2dfe918
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java
@@ -0,0 +1,174 @@
+package com.example.administrator.chatdemo.bean;
+
+import org.greenrobot.greendao.annotation.Entity;
+import org.greenrobot.greendao.annotation.Id;
+import org.greenrobot.greendao.annotation.Property;
+import org.greenrobot.greendao.annotation.Generated;
+
+/**
+ * Created by dwq on 2017/7/20/020.
+ * e-mail:lomapa@163.com
+ */
+@Entity
+public class ChatMessageBean {
+ @Id
+ private Long id;
+ @Property(nameInDb = "UserId")
+ private String UserId;
+ @Property(nameInDb = "UserName")
+ private String UserName;
+ @Property(nameInDb = "UserHeadIcon")
+ private String UserHeadIcon;
+ @Property(nameInDb = "UserContent")
+ private String UserContent;
+ @Property(nameInDb = "time")
+ private long time;
+ @Property(nameInDb = "type")
+ private int type;
+ @Property(nameInDb = "messagetype")
+ private int messagetype;
+ @Property(nameInDb = "UserVoiceTime")
+ private float UserVoiceTime;
+ @Property(nameInDb = "UserVoicePath")
+ private String UserVoicePath;
+ @Property(nameInDb = "UserVoiceUrl")
+ private String UserVoiceUrl;
+ @Property(nameInDb = "sendState")
+ private
+ @ChatConst.SendState
+ int sendState;
+ @Property(nameInDb = "imageUrl")
+ private String imageUrl;
+ @Property(nameInDb = "imageIconUrl")
+ private String imageIconUrl;
+ @Property(nameInDb = "imageLocal")
+ private String imageLocal;
+ @Property(nameInDb = "isListened")
+ private boolean isListened;
+ @Generated(hash = 595418362)
+ public ChatMessageBean(Long id, String UserId, String UserName,
+ String UserHeadIcon, String UserContent, long time, int type,
+ int messagetype, float UserVoiceTime, String UserVoicePath,
+ String UserVoiceUrl, int sendState, String imageUrl,
+ String imageIconUrl, String imageLocal, boolean isListened) {
+ this.id = id;
+ this.UserId = UserId;
+ this.UserName = UserName;
+ this.UserHeadIcon = UserHeadIcon;
+ this.UserContent = UserContent;
+ this.time = time;
+ this.type = type;
+ this.messagetype = messagetype;
+ this.UserVoiceTime = UserVoiceTime;
+ this.UserVoicePath = UserVoicePath;
+ this.UserVoiceUrl = UserVoiceUrl;
+ this.sendState = sendState;
+ this.imageUrl = imageUrl;
+ this.imageIconUrl = imageIconUrl;
+ this.imageLocal = imageLocal;
+ this.isListened = isListened;
+ }
+ @Generated(hash = 1557449535)
+ public ChatMessageBean() {
+ }
+ public Long getId() {
+ return this.id;
+ }
+ public void setId(Long id) {
+ this.id = id;
+ }
+ public String getUserId() {
+ return this.UserId;
+ }
+ public void setUserId(String UserId) {
+ this.UserId = UserId;
+ }
+ public String getUserName() {
+ return this.UserName;
+ }
+ public void setUserName(String UserName) {
+ this.UserName = UserName;
+ }
+ public String getUserHeadIcon() {
+ return this.UserHeadIcon;
+ }
+ public void setUserHeadIcon(String UserHeadIcon) {
+ this.UserHeadIcon = UserHeadIcon;
+ }
+ public String getUserContent() {
+ return this.UserContent;
+ }
+ public void setUserContent(String UserContent) {
+ this.UserContent = UserContent;
+ }
+ public long getTime() {
+ return this.time;
+ }
+ public void setTime(long time) {
+ this.time = time;
+ }
+ public int getType() {
+ return this.type;
+ }
+ public void setType(int type) {
+ this.type = type;
+ }
+ public int getMessagetype() {
+ return this.messagetype;
+ }
+ public void setMessagetype(int messagetype) {
+ this.messagetype = messagetype;
+ }
+ public float getUserVoiceTime() {
+ return this.UserVoiceTime;
+ }
+ public void setUserVoiceTime(float UserVoiceTime) {
+ this.UserVoiceTime = UserVoiceTime;
+ }
+ public String getUserVoicePath() {
+ return this.UserVoicePath;
+ }
+ public void setUserVoicePath(String UserVoicePath) {
+ this.UserVoicePath = UserVoicePath;
+ }
+ public String getUserVoiceUrl() {
+ return this.UserVoiceUrl;
+ }
+ public void setUserVoiceUrl(String UserVoiceUrl) {
+ this.UserVoiceUrl = UserVoiceUrl;
+ }
+ public int getSendState() {
+ return this.sendState;
+ }
+ public void setSendState(int sendState) {
+ this.sendState = sendState;
+ }
+ public String getImageUrl() {
+ return this.imageUrl;
+ }
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+ public String getImageIconUrl() {
+ return this.imageIconUrl;
+ }
+ public void setImageIconUrl(String imageIconUrl) {
+ this.imageIconUrl = imageIconUrl;
+ }
+ public String getImageLocal() {
+ return this.imageLocal;
+ }
+ public void setImageLocal(String imageLocal) {
+ this.imageLocal = imageLocal;
+ }
+ public boolean getIsListened() {
+ return this.isListened;
+ }
+ public void setIsListened(boolean isListened) {
+ this.isListened = isListened;
+ }
+
+
+
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java
new file mode 100644
index 0000000..bf5a709
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java
@@ -0,0 +1,36 @@
+package com.example.administrator.chatdemo.bean;
+
+/**
+ * Created by dwq on 2017/7/20/020.
+ * e-mail:lomapa@163.com
+ */
+
+public enum ChatMessageType {
+ UnsupportedMessageType(0),
+ TextMessageType(-1),
+ ImageMessageType(-2),
+ AudioMessageType(-3);
+
+ int type;
+
+ private ChatMessageType(int type) {
+ this.type = type;
+ }
+
+ public int getType() {
+ return this.type;
+ }
+
+ public static ChatMessageType getAVIMReservedMessageType(int type) {
+ switch (type) {
+ case -3:
+ return AudioMessageType;
+ case -2:
+ return ImageMessageType;
+ case -1:
+ return TextMessageType;
+ default:
+ return UnsupportedMessageType;
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java b/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java
new file mode 100644
index 0000000..eff9bf3
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java
@@ -0,0 +1,212 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.media.MediaRecorder;
+import android.os.Handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AudioManager {
+ /**
+ * 录音的时候出错
+ */
+ public static final int MSG_ERROR_AUDIO_RECORD = -4;
+ private MediaRecorder mRecorder;
+ private String mDirString;
+ private String mCurrentFilePathString;
+ private Handler handler;
+ private boolean isPrepared;// 是否准备好了
+
+ /**
+ * 单例化的方法 1 先声明一个static 类型的变量a 2 在声明默认的构造函数 3 再用public synchronized static
+ * 类名 getInstance() { if(a==null) { a=new 类();} return a; } 或者用以下的方法
+ */
+
+ /**
+ * 单例化这个类
+ */
+ private static AudioManager mInstance;
+
+ private AudioManager(String dir) {
+ mDirString = dir;
+ }
+
+ public static AudioManager getInstance(String dir) {
+ if (mInstance == null) {
+ synchronized (AudioManager.class) {
+ if (mInstance == null) {
+ mInstance = new AudioManager(dir);
+
+ }
+ }
+ }
+ return mInstance;
+
+ }
+
+ public void setHandle(Handler handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * 回调函数,准备完毕,准备好后,button才会开始显示录音框
+ */
+ public interface AudioStageListener {
+ void wellPrepared();
+ }
+
+ public AudioStageListener mListener;
+
+ public void setOnAudioStageListener(AudioStageListener listener) {
+ mListener = listener;
+ }
+
+ public void setVocDir(String dir) {
+ mDirString = dir;
+ }
+
+ // 准备方法
+ @SuppressWarnings("deprecation")
+ public void prepareAudio() {
+ try {
+ // 一开始应该是false的
+ isPrepared = false;
+
+ File dir = new File(mDirString);
+// if (!dir.exists()) {
+// dir.mkdirs();
+// }
+//
+ String fileNameString = generalFileName();
+ File file = new File(dir, fileNameString);
+ mCurrentFilePathString = file.getAbsolutePath();
+ mRecorder = new MediaRecorder();
+ // 设置输出文件
+ mRecorder.setOutputFile(file.getAbsolutePath());
+ // 设置meidaRecorder的音频源是麦克风
+ mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ // 设置文件音频的输出格式为amr
+ mRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
+ // 设置音频的编码格式为amr
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+
+ // 严格遵守google官方api给出的mediaRecorder的状态流程图
+ mRecorder.prepare();
+
+ mRecorder.start();
+ // 准备结束
+ // 已经准备好了,可以录制了
+ if (mListener != null) {
+ mListener.wellPrepared();
+ }
+ isPrepared = true;
+
+ } catch (IllegalStateException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ if (handler != null) {
+ handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ if (handler != null) {
+ handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ if (handler != null) {
+ handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
+ }
+ }
+
+ }
+
+ /**
+ * 随机生成文件的名称
+ *
+ * @return
+ */
+ private String generalFileName() {
+ // TODO Auto-generated method stub
+
+// return UUID.randomUUID().toString() + ".amr";
+ return String.valueOf(System.currentTimeMillis());
+ }
+
+ private int vocAuthority[] = new int[10];
+ private int vocNum = 0;
+ private boolean check = true;
+
+ // 获得声音的level
+ public int getVoiceLevel(int maxLevel) {
+ // mRecorder.getMaxAmplitude()这个是音频的振幅范围,值域是0-32767
+ if (isPrepared) {
+ try {
+ int vocLevel = mRecorder.getMaxAmplitude();
+ if (check) {
+ if (vocNum >= 10) {
+ Set set = new HashSet();
+ for (int i = 0; i < vocNum; i++) {
+ set.add(vocAuthority[i]);
+ }
+ if (set.size() == 1) {
+ if (handler != null)
+ handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
+ vocNum = 0;
+ vocAuthority = null;
+ vocAuthority = new int[10];
+ } else {
+ check = false;
+ }
+ } else {
+ vocAuthority[vocNum] = vocLevel;
+ vocNum++;
+ }
+ }
+ return maxLevel * vocLevel / 32768 + 1;
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ if (handler != null)
+ handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
+ }
+ }
+
+ return 1;
+ }
+
+ // 释放资源
+ public void release() {
+ // 严格按照api流程进行
+ if (null != mRecorder) {
+ isPrepared = false;
+ try {
+ mRecorder.stop();
+ mRecorder.release();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mRecorder = null;
+ }
+ }
+
+ // 取消,因为prepare时产生了一个文件,所以cancel方法应该要删除这个文件,
+ // 这是与release的方法的区别
+ public void cancel() {
+ release();
+ if (mCurrentFilePathString != null) {
+ File file = new File(mCurrentFilePathString);
+ file.delete();
+ mCurrentFilePathString = null;
+ }
+
+ }
+
+ public String getCurrentFilePath() {
+ // TODO Auto-generated method stub
+ return mCurrentFilePathString;
+ }
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java b/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java
new file mode 100644
index 0000000..54addf1
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java
@@ -0,0 +1,47 @@
+package com.example.administrator.chatdemo.utils;
+
+/**
+ * Created by wli on 15/8/23.
+ * 用来存放各种 static final 值
+ */
+public class Constants {
+
+ public static final String OBJECT_ID = "objectId";
+ public static final int PAGE_SIZE = 10;
+ public static final String CREATED_AT = "createdAt";
+ public static final String UPDATED_AT = "updatedAt";
+
+
+ //TODO 还不知道这俩货是干嘛的
+ public static final int ORDER_UPDATED_AT = 1;
+ public static final int ORDER_DISTANCE = 0;
+
+ private static final String LEANMESSAGE_CONSTANTS_PREFIX = "com.avoscloud.leanchatlib.";
+
+ public static final String MEMBER_ID = getPrefixConstant("member_id");
+ public static final String CONVERSATION_ID = getPrefixConstant("conversation_id");
+
+ public static final String LEANCHAT_USER_ID = getPrefixConstant("leanchat_user_id");
+
+ public static final String ACTIVITY_TITLE = getPrefixConstant("activity_title");
+
+ public static final String INTENT_KEY = getPrefixConstant("intent_key");
+ public static final String INTENT_VALUE = getPrefixConstant("intent_value");
+ public static final String INTENT_DATA = getPrefixConstant("intent_data");
+
+
+
+ // ImageBrowserActivity
+ public static final String IMAGE_LOCAL_PATH = getPrefixConstant("image_local_path");
+ public static final String IMAGE_URL = getPrefixConstant("image_url");
+
+ //Notification
+ public static final String NOTOFICATION_TAG = getPrefixConstant("notification_tag");
+ public static final String NOTIFICATION_SINGLE_CHAT = Constants.getPrefixConstant("notification_single_chat");
+ public static final String NOTIFICATION_GROUP_CHAT = Constants.getPrefixConstant("notification_group_chat");
+ public static final String NOTIFICATION_SYSTEM = Constants.getPrefixConstant("notification_system_chat");
+
+ public static String getPrefixConstant(String str) {
+ return LEANMESSAGE_CONSTANTS_PREFIX + str;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java b/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java
new file mode 100644
index 0000000..61eedfe
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java
@@ -0,0 +1,205 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ImageSpan;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Created by lzw on 14-9-25.
+ */
+public class EmotionHelper {
+ private static final int ONE_PAGE_SIZE = 24;
+ public static List> emojiGroups;
+ private static Pattern pattern;
+ private static String[] emojiCodes = new String[]{
+ ":smile:",
+ ":laughing:",
+ ":blush:",
+ ":smiley:",
+ ":relaxed:",
+ ":smirk:",
+ ":heart_eyes:",
+ ":kissing_heart:",
+ ":kissing_closed_eyes:",
+ ":flushed:",
+ ":relieved:",
+ ":satisfied:",
+ ":grin:",
+ ":wink:",
+ ":stuck_out_tongue_winking_eye:",
+ ":stuck_out_tongue_closed_eyes:",
+ ":grinning:",
+ ":kissing:",
+ ":kissing_smiling_eyes:",
+ ":stuck_out_tongue:",
+ ":sleeping:",
+ ":worried:",
+ ":frowning:",
+ ":anguished:",
+ ":open_mouth:",
+ ":grimacing:",
+ ":confused:",
+ ":hushed:",
+ ":expressionless:",
+ ":unamused:",
+ ":sweat_smile:",
+ ":sweat:",
+ ":disappointed_relieved:",
+ ":weary:",
+ ":pensive:",
+ ":disappointed:",
+ ":confounded:",
+ ":fearful:",
+ ":cold_sweat:",
+ ":persevere:",
+ ":cry:",
+ ":sob:",
+ ":joy:",
+ ":astonished:",
+ ":scream:",
+ ":tired_face:",
+ ":angry:",
+ ":rage:",
+ ":triumph:",
+ ":sleepy:",
+ ":yum:",
+ ":mask:",
+ ":sunglasses:",
+ ":dizzy_face:",
+ ":neutral_face:",
+ ":no_mouth:",
+ ":innocent:",
+ ":thumbsup:",
+ ":thumbsdown:",
+ ":clap:",
+ ":point_right:",
+ ":point_left:"};
+
+ static {
+ int pages = emojiCodes.length / ONE_PAGE_SIZE + (emojiCodes.length % ONE_PAGE_SIZE == 0 ? 0 : 1);
+ emojiGroups = new ArrayList<>();
+ for (int page = 0; page < pages; page++) {
+ List onePageEmojis = new ArrayList<>();
+ int start = page * ONE_PAGE_SIZE;
+ int end = Math.min(page * ONE_PAGE_SIZE + ONE_PAGE_SIZE, emojiCodes.length);
+ for (int i = start; i < end; i++) {
+ onePageEmojis.add(emojiCodes[i]);
+ }
+ emojiGroups.add(onePageEmojis);
+ }
+ pattern = Pattern.compile("\\:[a-z0-9-_]*\\:");
+ }
+
+ public static boolean contain(String[] strings, String string) {
+ for (String s : strings) {
+ if (s.equals(string)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static CharSequence replace(Context context, String text) {
+ if (TextUtils.isEmpty(text)) {
+ return text;
+ }
+ SpannableString spannableString = new SpannableString(text);
+ Matcher matcher = pattern.matcher(text);
+ while (matcher.find()) {
+ String factText = matcher.group();
+ String key = factText.substring(1, factText.length() - 1);
+ if (contain(emojiCodes, factText)) {
+ Bitmap bitmap = getEmojiDrawableInText(context, key);
+ ImageSpan image = new ImageSpan(context, bitmap);
+ int start = matcher.start();
+ int end = matcher.end();
+ spannableString.setSpan(image, start, end,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ return spannableString;
+ }
+
+ public static SpannableString replaceEmoj(Context context, String text) {
+ if (TextUtils.isEmpty(text)) {
+ return null;
+ }
+ SpannableString spannableString = new SpannableString(text);
+ Matcher matcher = pattern.matcher(text);
+ while (matcher.find()) {
+ String factText = matcher.group();
+ String key = factText.substring(1, factText.length() - 1);
+ if (contain(emojiCodes, factText)) {
+ Bitmap bitmap = getEmojiDrawable(context, key);
+ ImageSpan image = new ImageSpan(context, bitmap);
+ int start = matcher.start();
+ int end = matcher.end();
+ spannableString.setSpan(image, start, end,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ return spannableString;
+ }
+
+ public static void isEmojiDrawableAvailable(Context context) {
+ for (String emojiCode : emojiCodes) {
+ String code = emojiCode.substring(1, emojiCode.length() - 1);
+ Bitmap bitmap = getDrawableByName(context, code);
+ if (bitmap == null) {
+ Log.i("EmotionHelper", "not available test " + code);
+ }
+ }
+ }
+
+ public static Bitmap getEmojiDrawableInText(Context context, String name) {
+ return getDrawableByNameInText(context, "emoji_" + name);
+ }
+
+ public static Bitmap getEmojiDrawable(Context context, String name) {
+ return getDrawableByName(context, "emoji_" + name);
+ }
+
+ public static Bitmap getDrawableByName(Context ctx, String name) {
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ Matrix matrix = new Matrix();
+ matrix.postScale(0.8f, 0.8f);
+ Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(),
+ ctx.getResources().getIdentifier(name, "drawable",
+ ctx.getPackageName()), options);
+ Bitmap BitmapOrg = bitmap;
+ int width = BitmapOrg.getWidth();
+ int height = BitmapOrg.getHeight();
+ Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width,
+ height, matrix, true);
+ return resizedBitmap;
+ }
+
+ public static Bitmap getDrawableByNameInText(Context ctx, String name) {
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ Matrix matrix = new Matrix();
+ matrix.postScale(0.58f, 0.58f);
+ Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(),
+ ctx.getResources().getIdentifier(name, "drawable",
+ ctx.getPackageName()), options);
+ Bitmap BitmapOrg = bitmap;
+ int width = BitmapOrg.getWidth();
+ int height = BitmapOrg.getHeight();
+ Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width,
+ height, matrix, true);
+ return resizedBitmap;
+ }
+}
+
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java
new file mode 100644
index 0000000..04523af
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java
@@ -0,0 +1,372 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.util.Base64;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileSaveUtil {
+ public static final String SD_CARD_PATH = Environment.getExternalStorageDirectory().toString() + "/MAXI/";
+
+ // public static final String saveFn = SD_CARD_PATH
+// + "/user_chat_data/";
+// public static final String savelistFn = SD_CARD_PATH
+// + "/user_chat_data/chatList/";
+// public static final String savechannelFn = SD_CARD_PATH
+// + "/user_chat_data/channel_id/";
+// public static final String saveUnReadFn = SD_CARD_PATH
+// + "/user_chat_data/UnRead/";
+ public static final String voice_dir = SD_CARD_PATH
+ + "/voice_data/";
+
+ /**
+ * SD卡是否存在
+ **/
+ private boolean hasSD = false;
+ /**
+ * 当前程序包的路径
+ **/
+ private String FILESPATH;
+
+ public static boolean isFileExists(File file) {
+ if (!file.exists()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 获取文件夹下的所有文件名
+ */
+ public static List getFileName(String fileName) {
+ List fileList = new ArrayList();
+ String path = fileName; // 路径
+ File f = new File(path);
+ if (!f.exists()) {
+ System.out.println(path + " not exists");
+ return null;
+ }
+
+ File fa[] = f.listFiles();
+ for (int i = 0; i < fa.length; i++) {
+ File fs = fa[i];
+ if (!fs.isDirectory()) {
+ fileList.add(fs.getName());
+ }
+ }
+ return fileList;
+ }
+
+ /**
+ * 在SD卡上创建文件
+ *
+ * @throws IOException
+ */
+ public static File createSDFile(String fileName) throws IOException {
+ File file = new File(fileName);
+ if (!isFileExists(file))
+ if (file.isDirectory()) {
+ file.mkdirs();
+ } else {
+ file.createNewFile();
+ }
+ return file;
+ }
+
+ /**
+ * 在SD卡上创建文件夹
+ *
+ * @throws IOException
+ */
+ public static File createSDDirectory(String fileName) throws IOException {
+ File file = new File(fileName);
+ if (!isFileExists(file))
+ file.mkdirs();
+ return file;
+ }
+
+// /**
+// * @content 存储内容
+// * @file 文件目录
+// * @isAppend 是否追加
+// */
+// public synchronized static void writeString(String content, String file, boolean isAppend) {
+// try {
+// createSDDirectory(saveFn);
+// createSDDirectory(savelistFn);
+// createSDDirectory(savechannelFn);
+// byte[] data = content.getBytes("utf-8");
+// writeBytes(file, data, isAppend);
+// } catch (Exception e) {
+// System.out.println(e.getMessage());
+// }
+//
+// }
+
+ public synchronized static boolean writeBytes(String filePath, byte[] data,
+ boolean isAppend) {
+ try {
+ FileOutputStream fos;
+ if (isAppend)
+ fos = new FileOutputStream(filePath, true);
+ else
+ fos = new FileOutputStream(filePath);
+ fos.write(data);
+ fos.close();
+ return true;
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ return false;
+ }
+
+ /**
+ * 读取SD卡中文本文件
+ *
+ * @param fileName
+ * @return
+ */
+ public synchronized static String readSDFile(String fileName) {
+ StringBuffer sb = new StringBuffer();
+ File f1 = new File(fileName);
+ String str = null;
+ try {
+ InputStream is = new FileInputStream(f1);
+ InputStreamReader input = new InputStreamReader(is, "UTF-8");
+ @SuppressWarnings("resource")
+ BufferedReader reader = new BufferedReader(input);
+ while ((str = reader.readLine()) != null) {
+ sb.append(str);
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return sb.toString();
+ }
+
+ public String getFILESPATH() {
+ return FILESPATH;
+ }
+
+ public boolean hasSD() {
+ return hasSD;
+ }
+
+ /**
+ * 删除单个文件
+ *
+ * @param filePath 被删除文件的文件名
+ * @return 文件删除成功返回true,否则返回false
+ */
+ public static boolean deleteFile(String filePath) {
+ File file = new File(filePath);
+ if (file.isFile() && file.exists()) {
+ return file.delete();
+ }
+ return false;
+ }
+
+ /**
+ * 删除文件夹以及目录下的文件
+ *
+ * @param filePath 被删除目录的文件路径
+ * @return 目录删除成功返回true,否则返回false
+ */
+ public static boolean deleteDirectory(String filePath) {
+ boolean flag = false;
+ // 如果filePath不以文件分隔符结尾,自动添加文件分隔符
+ if (!filePath.endsWith(File.separator)) {
+ filePath = filePath + File.separator;
+ }
+ File dirFile = new File(filePath);
+ if (!dirFile.exists() || !dirFile.isDirectory()) {
+ return false;
+ }
+ flag = true;
+ File[] files = dirFile.listFiles();
+ // 遍历删除文件夹下的所有文件(包括子目录)
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].isFile()) {
+ // 删除子文件
+ flag = deleteFile(files[i].getAbsolutePath());
+ if (!flag)
+ break;
+ } else {
+ // 删除子目录
+ flag = deleteDirectory(files[i].getAbsolutePath());
+ if (!flag)
+ break;
+ }
+ }
+ if (!flag)
+ return false;
+ // 删除当前空目录
+ return dirFile.delete();
+ }
+
+ public static boolean saveBitmap(Bitmap bm, String picName) {
+ try {
+ File f = new File(picName);
+ if (f.exists()) {
+ f.delete();
+ }
+ FileOutputStream out = new FileOutputStream(f);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ return true;
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return false;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 把文件转换成base64
+ *
+ * @param path
+ * @return
+ */
+ public static String encodeBase64File(String path) throws Exception {
+ byte[] videoBytes;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ @SuppressWarnings("resource")
+ FileInputStream fis = new FileInputStream(new File(path));
+ byte[] buf = new byte[1024];
+ int n;
+ while (-1 != (n = fis.read(buf)))
+ baos.write(buf, 0, n);
+ videoBytes = baos.toByteArray();
+ return Base64.encodeToString(videoBytes, Base64.NO_WRAP);
+ }
+
+ /**
+ * 根据相册媒体库路径转换成sd卡路径
+ *
+ * @param context
+ * @param uri
+ * @return
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static String getPath(final Context context, final Uri uri) {
+ final boolean isOverKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ // DocumentProvider
+ if (isOverKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/"
+ + split[1];
+ }
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"),
+ Long.valueOf(id));
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{split[1]};
+ return getDataColumn(context, contentUri, selection,
+ selectionArgs);
+ }
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ public static String getDataColumn(Context context, Uri uri,
+ String selection, String[] selectionArgs) {
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {column};
+ try {
+ cursor = context.getContentResolver().query(uri, projection,
+ selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri
+ .getAuthority());
+ }
+
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri
+ .getAuthority());
+ }
+
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri
+ .getAuthority());
+ }
+
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri
+ .getAuthority());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java
new file mode 100644
index 0000000..58997ae
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java
@@ -0,0 +1,61 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class ImageCheckoutUtil {
+ /**
+ * 检测图片内存大小
+ *
+ * @param data
+ * @return
+ */
+ @SuppressLint("NewApi")
+ public static int getImageSize(Bitmap data) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
+ return data.getRowBytes() * data.getHeight();
+ } else {
+ return data.getByteCount();
+ }
+ }
+
+ public static Bitmap getLoacalBitmap(String url) {
+ try {
+ ByteArrayOutputStream out;
+ FileInputStream fis = new FileInputStream(url);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ out = new ByteArrayOutputStream();
+ @SuppressWarnings("unused")
+ int hasRead = 0;
+ byte[] buffer = new byte[1024 * 2];
+ while ((hasRead = bis.read(buffer)) > 0) {
+ // 读出多少数据,向输出流中写入多少
+ out.write(buffer);
+ out.flush();
+ }
+ out.close();
+ fis.close();
+ bis.close();
+ byte[] data = out.toByteArray();
+ // 长宽减半
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inSampleSize = 3;
+ return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java
new file mode 100644
index 0000000..664f5fe
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java
@@ -0,0 +1,26 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.content.Context;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * Created by Mao Jiqing on 2016/9/28.
+ */
+public class KeyBoardUtils {
+ public static void hideKeyBoard(Context context, View view) {
+ InputMethodManager imm = (InputMethodManager) context
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏键盘
+ }
+
+ public static void showKeyBoard(Context context, View view) {
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(true);
+ view.requestFocus();
+ view.findFocus();
+ InputMethodManager imm = (InputMethodManager) context
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java
new file mode 100644
index 0000000..38aabff
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java
@@ -0,0 +1,67 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.util.Log;
+
+/**
+ * Created by lzw on 15/7/14.
+ */
+public class LogUtils {
+ public static final String LOGTAG = "leanchat";
+ public static boolean debugEnabled;
+
+ public LogUtils() {
+ }
+
+ private static String getDebugInfo() {
+ Throwable stack = new Throwable().fillInStackTrace();
+ StackTraceElement[] trace = stack.getStackTrace();
+ int n = 2;
+ return trace[n].getClassName() + " " + trace[n].getMethodName() + "()" + ":" + trace[n].getLineNumber() +
+ " ";
+ }
+
+ private static String getLogInfoByArray(String[] infos) {
+ StringBuilder sb = new StringBuilder();
+ for (String info : infos) {
+ sb.append(info);
+ sb.append(" ");
+ }
+ return sb.toString();
+ }
+
+ public static void i(String... s) {
+ if (debugEnabled) {
+ Log.i(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
+ }
+ }
+
+ public static void e(String... s) {
+ if (debugEnabled) {
+ Log.e(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
+ }
+ }
+
+ public static void d(String... s) {
+ if (debugEnabled) {
+ Log.d(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
+ }
+ }
+
+ public static void v(String... s) {
+ if (debugEnabled) {
+ Log.v(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
+ }
+ }
+
+ public static void w(String... s) {
+ if (debugEnabled) {
+ Log.w(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
+ }
+ }
+
+ public static void logException(Throwable tr) {
+ if (debugEnabled) {
+ Log.e(LOGTAG, getDebugInfo(), tr);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java
new file mode 100644
index 0000000..fba9602
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java
@@ -0,0 +1,94 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.content.Context;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.File;
+
+/**
+ * Created by lzw on 15/4/26.
+ */
+public class PathUtils {
+ private static File checkAndMkdirs(File file) {
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return file;
+ }
+
+ private static boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ /**
+ * 有 sdcard 的时候,小米是 /storage/sdcard0/Android/data/com.avoscloud.chat/cache/
+ * 无 sdcard 的时候,小米是 /data/data/com.avoscloud.chat/cache
+ * 依赖于包名。所以不同应用使用该库也没问题,要有点理想。
+ *
+ * @return
+ */
+ private static File getAvailableCacheDir(Context context) {
+ if (isExternalStorageWritable()) {
+ return context.getExternalCacheDir();
+ } else {
+ // 只有此应用才能访问。拍照的时候有问题,因为拍照的应用写入不了该文件
+ return context.getCacheDir();
+ }
+ }
+
+ /**
+ * 可能文件会被清除掉,需要检查是否存在
+ *
+ * @param id
+ * @return
+ */
+ public static String getChatFilePath(Context context, String id) {
+ return (TextUtils.isEmpty(id) ? null : new File(getAvailableCacheDir(context), id).getAbsolutePath());
+ }
+
+ /**
+ * 录音保存的地址
+ *
+ * @return
+ */
+ public static String getRecordPathByCurrentTime(Context context) {
+ return new File(getAvailableCacheDir(context), "record_" + System.currentTimeMillis()).getAbsolutePath();
+ }
+
+ /**
+ * 拍照保存的地址
+ *
+ * @return
+ */
+ public static String getPicturePathByCurrentTime(Context context) {
+ String path = new File(getAvailableCacheDir(context), "picture_" + System.currentTimeMillis()).getAbsolutePath();
+// LogUtils.d("picture path ", path);
+ return path;
+ }
+
+ public static String getSavePicPath(Context context) {
+ final String dir = getAvailableCacheDir(context) + "/image_data/";
+ File file = new File(dir);
+ if (!isFileExists(file))
+ file.mkdirs();
+ String fileName = String.valueOf(System.currentTimeMillis() + ".png");
+ return dir + fileName;
+ }
+
+ public static String getSaveVoicePath(Context context) {
+ final String dir = getAvailableCacheDir(context) + "/voice_data/";
+ File file = new File(dir);
+ if (!isFileExists(file))
+ file.mkdirs();
+ return dir;
+ }
+
+ public static boolean isFileExists(File file) {
+ if (!file.exists()) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java
new file mode 100644
index 0000000..c63755c
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java
@@ -0,0 +1,372 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.media.ThumbnailUtils;
+import android.widget.ImageView;
+
+import com.example.administrator.chatdemo.R;
+import com.nostra13.universalimageloader.core.DisplayImageOptions;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.assist.ImageScaleType;
+import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Created by lzw on 15/4/24.
+ * TODO Util 部分还需要再优化
+ */
+public class PhotoUtils {
+ public static DisplayImageOptions avatarImageOptions = new DisplayImageOptions.Builder()
+ .showImageOnLoading(R.drawable.chat_default_user_avatar)
+ .showImageForEmptyUri(R.drawable.chat_default_user_avatar)
+ .showImageOnFail(R.drawable.chat_default_user_avatar)
+ .cacheInMemory(true)
+ .cacheOnDisc(true)
+ .considerExifParams(true)
+ .imageScaleType(ImageScaleType.EXACTLY)
+ .bitmapConfig(Bitmap.Config.RGB_565)
+ .resetViewBeforeLoading(true)// 设置图片在下载前是否重置,复位
+ //.displayer(new RoundedBitmapDisplayer(20))
+ //.displayer(new FadeInBitmapDisplayer(100))// 淡入
+ .build();
+
+ private static DisplayImageOptions normalImageOptions = new DisplayImageOptions.Builder()
+ .showImageOnLoading(R.drawable.chat_common_empty_photo)
+ .showImageForEmptyUri(R.drawable.chat_common_empty_photo)
+ .showImageOnFail(R.drawable.chat_common_image_load_fail)
+ .cacheInMemory(true)
+ .cacheOnDisc(true)
+ .considerExifParams(true)
+ .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
+ .bitmapConfig(Bitmap.Config.RGB_565)
+ .resetViewBeforeLoading(true)// 设置图片在下载前是否重置,复位
+ //.displayer(new RoundedBitmapDisplayer(20))
+ .displayer(new FadeInBitmapDisplayer(100))// 淡入
+ .build();
+
+ public static void displayImageCacheElseNetwork(ImageView imageView,
+ String path, String url) {
+ ImageLoader imageLoader = ImageLoader.getInstance();
+ if (path != null) {
+ File file = new File(path);
+ if (file.exists()) {
+ imageLoader.displayImage("file://" + path, imageView, normalImageOptions);
+ return;
+ }
+ }
+ imageLoader.displayImage(url, imageView, normalImageOptions);
+ }
+
+ public static String compressImage(String path, String newPath) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(path, options);
+ int inSampleSize = 1;
+ int maxSize = 3000;
+ if (options.outWidth > maxSize || options.outHeight > maxSize) {
+ int widthScale = (int) Math.ceil(options.outWidth * 1.0 / maxSize);
+ int heightScale = (int) Math.ceil(options.outHeight * 1.0 / maxSize);
+ inSampleSize = Math.max(widthScale, heightScale);
+ }
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = inSampleSize;
+ Bitmap bitmap = BitmapFactory.decodeFile(path, options);
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int newW = w;
+ int newH = h;
+ if (w > maxSize || h > maxSize) {
+ if (w > h) {
+ newW = maxSize;
+ newH = (int) (newW * h * 1.0 / w);
+ } else {
+ newH = maxSize;
+ newW = (int) (newH * w * 1.0 / h);
+ }
+ }
+ Bitmap newBitmap = Bitmap.createScaledBitmap(bitmap, newW, newH, false);
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(newPath);
+ newBitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
+ } catch (FileNotFoundException e) {
+ LogUtils.logException(e);
+ } finally {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ recycle(newBitmap);
+ recycle(bitmap);
+ return newPath;
+ }
+
+ public static void recycle(Bitmap bitmap) {
+ // 先判断是否已经回收
+ if (bitmap != null && !bitmap.isRecycled()) {
+ // 回收并且置为null
+ bitmap.recycle();
+ }
+ System.gc();
+ }
+
+ /**
+ * 获取指定路径下的图片的指定大小的缩略图 getImageThumbnail
+ *
+ * @return Bitmap
+ * @throws
+ */
+ public static Bitmap getImageThumbnail(String imagePath, int width,
+ int height) {
+ Bitmap bitmap = null;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ // 获取这个图片的宽和高,注意此处的bitmap为null
+ bitmap = BitmapFactory.decodeFile(imagePath, options);
+ options.inJustDecodeBounds = false; // 设为 false
+ // 计算缩放比
+ int h = options.outHeight;
+ int w = options.outWidth;
+ int beWidth = w / width;
+ int beHeight = h / height;
+ int be = 1;
+ if (beWidth < beHeight) {
+ be = beWidth;
+ } else {
+ be = beHeight;
+ }
+ if (be <= 0) {
+ be = 1;
+ }
+ options.inSampleSize = be;
+ // 重新读入图片,读取缩放后的bitmap,注意这次要把options.inJustDecodeBounds 设为 false
+ bitmap = BitmapFactory.decodeFile(imagePath, options);
+ // 利用ThumbnailUtils来创建缩略图,这里要指定要缩放哪个Bitmap对象
+ bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height,
+ ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
+ return bitmap;
+ }
+
+ public static void saveBitmap(String filePath,
+ Bitmap bitmap) {
+ File file = new File(filePath);
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(file);
+ if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+ out.flush();
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ Utils.closeQuietly(out);
+ }
+ }
+
+ public static File getFilePath(String filePath, String fileName) {
+ File file = null;
+ makeRootDirectory(filePath);
+ try {
+ file = new File(filePath + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ LogUtils.logException(e);
+ }
+ return file;
+ }
+
+ public static void makeRootDirectory(String filePath) {
+ File file = null;
+ try {
+ file = new File(filePath);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ } catch (Exception e) {
+
+ }
+ }
+
+ /**
+ * 读取图片属性:旋转的角度
+ *
+ * @param path 图片绝对路径
+ * @return degree旋转的角度
+ */
+
+ public static int readPictureDegree(String path) {
+ int degree = 0;
+ try {
+ ExifInterface exifInterface = new ExifInterface(path);
+ int orientation = exifInterface.getAttributeInt(
+ ExifInterface.TAG_ORIENTATION,
+ ExifInterface.ORIENTATION_NORMAL);
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ degree = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ degree = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ degree = 270;
+ break;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return degree;
+
+ }
+
+ /**
+ * 旋转图片一定角度
+ * rotaingImageView
+ *
+ * @return Bitmap
+ * @throws
+ */
+ public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
+ // 旋转图片 动作
+ Matrix matrix = new Matrix();
+ matrix.postRotate(angle);
+ // 创建新的图片
+ Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
+ bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ return resizedBitmap;
+ }
+
+ /**
+ * 将图片变为圆角
+ *
+ * @param bitmap 原Bitmap图片
+ * @param pixels 图片圆角的弧度(单位:像素(px))
+ * @return 带有圆角的图片(Bitmap 类型)
+ */
+ public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
+ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+ bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+
+ final int color = 0xff424242;
+ final Paint paint = new Paint();
+ final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ final RectF rectF = new RectF(rect);
+ final float roundPx = pixels;
+
+ paint.setAntiAlias(true);
+ canvas.drawARGB(0, 0, 0, 0);
+ paint.setColor(color);
+ canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(bitmap, rect, rect, paint);
+
+ return output;
+ }
+
+ /**
+ * 将图片转化为圆形头像
+ *
+ * @throws
+ * @Title: toRoundBitmap
+ */
+ public static Bitmap toRoundBitmap(Bitmap bitmap) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ float roundPx;
+ float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom;
+ if (width <= height) {
+ roundPx = width / 2;
+
+ left = 0;
+ top = 0;
+ right = width;
+ bottom = width;
+
+ height = width;
+
+ dst_left = 0;
+ dst_top = 0;
+ dst_right = width;
+ dst_bottom = width;
+ } else {
+ roundPx = height / 2;
+
+ float clip = (width - height) / 2;
+
+ left = clip;
+ right = width - clip;
+ top = 0;
+ bottom = height;
+ width = height;
+
+ dst_left = 0;
+ dst_top = 0;
+ dst_right = height;
+ dst_bottom = height;
+ }
+
+ Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+
+ final Paint paint = new Paint();
+ final Rect src = new Rect((int) left, (int) top, (int) right,
+ (int) bottom);
+ final Rect dst = new Rect((int) dst_left, (int) dst_top,
+ (int) dst_right, (int) dst_bottom);
+ final RectF rectF = new RectF(dst);
+
+ paint.setAntiAlias(true);// 设置画笔无锯齿
+
+ canvas.drawARGB(0, 0, 0, 0); // 填充整个Canvas
+
+ // 以下有两种方法画圆,drawRounRect和drawCircle
+ canvas.drawRoundRect(rectF, roundPx, roundPx, paint);// 画圆角矩形,第一个参数为图形显示区域,第二个参数和第三个参数分别是水平圆角半径和垂直圆角半径。
+ // canvas.drawCircle(roundPx, roundPx, roundPx, paint);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 设置两张图片相交时的模式,参考http://trylovecatch.iteye.com/blog/1189452
+ canvas.drawBitmap(bitmap, src, dst, paint); // 以Mode.SRC_IN模式合并bitmap和已经draw了的Circle
+
+ return output;
+ }
+
+ public static String simpleCompressImage(String path, String newPath) {
+ Bitmap bitmap = BitmapFactory.decodeFile(path);
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(newPath);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ Utils.closeQuietly(outputStream);
+ }
+ recycle(bitmap);
+ return newPath;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java
new file mode 100644
index 0000000..771e5c7
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java
@@ -0,0 +1,178 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Window;
+import android.view.WindowManager;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class ScreenUtil {
+
+ private static int screenWidth = 0;
+
+ private static int screenHeight = 0;
+ private static int screenTotalHeight = 0;
+ private static int statusBarHeight = 0;
+
+ private static final int TITLE_HEIGHT = 0;
+
+ /**
+ */
+ public static int dip2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ /**
+ */
+ public static int px2dip(Context context, float pxValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (pxValue / scale + 0.5f);
+ }
+
+ public static int getScreenWidth(Context context) {
+// if (screenWidth != 0) {
+// return screenWidth;
+// }
+ DisplayMetrics dm = new DisplayMetrics();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(dm);
+ screenWidth = dm.widthPixels;
+ return screenWidth;
+ }
+
+ public static int getScreenHeight(Context context) {
+// if (screenHeight != 0) {
+// return screenHeight;
+// }
+ int top = 0;
+ if (context instanceof Activity) {
+ top = ((Activity) context).getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
+ if (top == 0) {
+ top = (int) (TITLE_HEIGHT * getScreenDensity(context));
+ }
+ }
+ DisplayMetrics dm = new DisplayMetrics();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(dm);
+ screenHeight = dm.heightPixels - top;
+ return screenHeight;
+ }
+
+ public static int getScreenTotalHeight(Context context) {
+ if (screenTotalHeight != 0) {
+ return screenTotalHeight;
+ }
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ screenTotalHeight = displayMetrics.heightPixels;
+ return screenTotalHeight;
+ }
+ public static float getScreenDensity(Context context) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metric = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metric);
+ return metric.density;
+ }
+
+ public static float getScreenDensityDpi(Context context) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metric = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metric);
+ return metric.densityDpi;
+ }
+ public static int getStatusBarHeight(Context context) {
+ if (statusBarHeight != 0) {
+ return statusBarHeight;
+ }
+ Class> c = null;
+ Object obj = null;
+ Field field = null;
+ int x = 0;
+ try {
+ c = Class.forName("com.android.internal.R$dimen");
+ obj = c.newInstance();
+ field = c.getField("status_bar_height");
+ x = Integer.parseInt(field.get(obj).toString());
+ statusBarHeight = context.getResources().getDimensionPixelSize(x);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ return statusBarHeight;
+ }
+ public static boolean isTablet(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ }
+
+
+ //获取屏幕原始尺寸高度,包括虚拟功能键高度
+ public static int getDpi(Context context){
+ int dpi = 0;
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ @SuppressWarnings("rawtypes")
+ Class c;
+ try {
+ c = Class.forName("android.view.Display");
+ @SuppressWarnings("unchecked")
+ Method method = c.getMethod("getRealMetrics",DisplayMetrics.class);
+ method.invoke(display, displayMetrics);
+ dpi=displayMetrics.heightPixels;
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ return dpi;
+ }
+
+ /**
+ * 获取 虚拟按键的高度
+ * @param context
+ * @return
+ */
+ public static int getBottomStatusHeight(Context context){
+ int totalHeight = getDpi(context);
+
+ int contentHeight = getScreenHeight(context);
+
+ return totalHeight - contentHeight;
+ }
+
+ /**
+ * 标题栏高度
+ * @return
+ */
+ public static int getTitleHeight(Activity activity){
+ return activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
+ }
+
+ /**
+ * 获得状态栏的高度
+ *
+ * @param context
+ * @return
+ */
+ public static int getStatusHeight(Context context)
+ {
+
+ int statusHeight = -1;
+ try
+ {
+ Class> clazz = Class.forName("com.android.internal.R$dimen");
+ Object object = clazz.newInstance();
+ int height = Integer.parseInt(clazz.getField("status_bar_height")
+ .get(object).toString());
+ statusHeight = context.getResources().getDimensionPixelSize(height);
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return statusHeight;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java
new file mode 100644
index 0000000..0cbbf58
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java
@@ -0,0 +1,54 @@
+package com.example.administrator.chatdemo.utils;
+
+import android.content.Context;
+
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+
+import org.ocpsoft.prettytime.PrettyTime;
+
+import java.io.Closeable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Created by lzw on 15/4/27.
+ */
+public class Utils {
+
+ public static String millisecsToDateString(long timestamp) {
+ long gap = System.currentTimeMillis() - timestamp;
+ if (gap < 1000 * 60 * 60 * 24) {
+ String s = (new PrettyTime()).format(new Date(timestamp));
+ return s;
+ } else {
+ SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm");
+ return format.format(new Date(timestamp));
+ }
+ }
+
+ public static void closeQuietly(Closeable closeable) {
+ try {
+ closeable.close();
+ } catch (Exception e) {
+ }
+ }
+
+ public static CharSequence getMessageeShorthand(Context context, ChatMessageBean message) {
+ if (message != null) {
+ int messageType = ((ChatMessageBean) message).getMessagetype();
+ switch (messageType) {
+ case -1:
+ return EmotionHelper.replace(context, ((ChatMessageBean) message).getUserContent());
+ case -2:
+ return "[图片]";
+ case -3:
+ return "[语音]";
+
+ default:
+ return EmotionHelper.replace(context, ((ChatMessageBean) message).getUserContent());
+ }
+
+ }
+ return "[]";
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java
new file mode 100644
index 0000000..2590b2f
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java
@@ -0,0 +1,87 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.PlayButtonClickListener;
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.widget.PlayButton;
+
+
+/**
+ * Created by wli on 15/9/17.
+ */
+public class ChatItemAudioHolder extends ChatItemHolder {
+
+ public PlayButton playButton;
+ protected TextView durationView;
+ private ImageView ivAudioUnread;
+ private String path;
+
+ private int mMinItemWith;// 设置对话框的最大宽度和最小宽度
+ private int mMaxItemWith;
+
+ public ChatItemAudioHolder(Context context, ViewGroup root, boolean isLeft) {
+ super(context, root, isLeft);
+ }
+
+ @Override
+ public void initView() {
+ super.initView();
+ // 获取系统宽度
+ WindowManager wManager = (WindowManager) getContext()
+ .getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ wManager.getDefaultDisplay().getMetrics(outMetrics);
+ mMaxItemWith = (int) (outMetrics.widthPixels * 0.7f);
+ mMinItemWith = (int) (outMetrics.widthPixels * 0.2f);
+ if (isLeft) {
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_left_audio_layout, null));
+ ivAudioUnread = (ImageView) itemView.findViewById(R.id.chat_item_audio_unread);
+ } else {
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_audio_layout, null));
+ }
+ playButton = (PlayButton) itemView.findViewById(R.id.chat_item_audio_play_btn);
+ durationView = (TextView) itemView.findViewById(R.id.chat_item_audio_duration_view);
+ }
+
+ @Override
+ public void bindData(Object o) {
+ super.bindData(o);
+ if (o instanceof ChatMessageBean) {
+ final ChatMessageBean audioMessage = (ChatMessageBean) o;
+ durationView.setText(String.format("%.0f\"", audioMessage.getUserVoiceTime()));
+ ViewGroup.LayoutParams layoutParams = playButton.getLayoutParams();
+ layoutParams.width = (int) (mMinItemWith + mMaxItemWith / 60f
+ * audioMessage.getUserVoiceTime());
+ playButton.setLayoutParams(layoutParams);
+ String localFilePath = audioMessage.getUserVoicePath();
+ final boolean leftSide = audioMessage.getType() == 1;
+ if (!TextUtils.isEmpty(localFilePath)) {
+ path = localFilePath;
+ } else {
+ path = audioMessage.getUserVoiceUrl();
+// LocalCacheUtils.downloadFileAsync(audioMessage.getUserVoiceUrl(), path);
+ }
+ playButton.setPath(path);
+ if (leftSide && ivAudioUnread != null)
+ if (audioMessage.getIsListened()) {
+ ivAudioUnread.setVisibility(View.INVISIBLE);
+ }
+ playButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new PlayButtonClickListener(playButton, ivAudioUnread, audioMessage, leftSide, getContext(), path).onClick(v);
+ }
+ });
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java
new file mode 100644
index 0000000..0941063
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java
@@ -0,0 +1,105 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.bean.ChatConst;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.utils.Utils;
+
+/**
+ * Created by dwq on 2017/7/20/020.
+ * e-mail:lomapa@163.com
+ */
+
+public class ChatItemHolder extends CommonViewHolder {
+ protected boolean isLeft;
+
+ protected ImageView avatarView;
+ protected TextView timeView;
+ protected TextView nameView;
+ protected LinearLayout conventLayout;
+ protected FrameLayout statusLayout;
+ protected ProgressBar progressBar;
+ protected TextView statusView;
+ protected ImageView errorView;
+ protected ChatMessageBean message;
+
+ public ChatItemHolder(Context context, ViewGroup root, boolean isLeft) {
+ super(context, root, isLeft ? R.layout.chat_item_left_layout : R.layout.chat_item_right_layout);
+ this.isLeft = isLeft;
+ initView();
+ }
+
+ public void initView() {
+ if (isLeft) {
+ avatarView = (ImageView) itemView.findViewById(R.id.chat_left_iv_avatar);
+ timeView = (TextView) itemView.findViewById(R.id.chat_left_tv_time);
+ nameView = (TextView) itemView.findViewById(R.id.chat_left_tv_name);
+ conventLayout = (LinearLayout) itemView.findViewById(R.id.chat_left_layout_content);
+ statusLayout = (FrameLayout) itemView.findViewById(R.id.chat_left_layout_status);
+ statusView = (TextView) itemView.findViewById(R.id.chat_left_tv_status);
+ progressBar = (ProgressBar) itemView.findViewById(R.id.chat_left_progressbar);
+ errorView = (ImageView) itemView.findViewById(R.id.chat_left_tv_error);
+ } else {
+ avatarView = (ImageView) itemView.findViewById(R.id.chat_right_iv_avatar);
+ timeView = (TextView) itemView.findViewById(R.id.chat_right_tv_time);
+ nameView = (TextView) itemView.findViewById(R.id.chat_right_tv_name);
+ conventLayout = (LinearLayout) itemView.findViewById(R.id.chat_right_layout_content);
+ statusLayout = (FrameLayout) itemView.findViewById(R.id.chat_right_layout_status);
+ progressBar = (ProgressBar) itemView.findViewById(R.id.chat_right_progressbar);
+ errorView = (ImageView) itemView.findViewById(R.id.chat_right_tv_error);
+ statusView = (TextView) itemView.findViewById(R.id.chat_right_tv_status);
+ }
+ }
+
+ @Override
+ public void bindData(Object o) {
+ message = (ChatMessageBean) o;
+ timeView.setText(Utils.millisecsToDateString(message.getTime()));
+
+ String userId = message.getUserId();
+ nameView.setText(message.getUserName());
+ if (isLeft) {
+ avatarView.setImageResource(R.mipmap.ic_head_01);
+ } else {
+ avatarView.setImageResource(R.mipmap.ic_head_02);
+ }
+ switch (message.getSendState()) {
+ case ChatConst.SENDING:
+ statusLayout.setVisibility(View.VISIBLE);
+ progressBar.setVisibility(View.VISIBLE);
+ statusView.setVisibility(View.GONE);
+ errorView.setVisibility(View.GONE);
+ break;
+ case ChatConst.COMPLETED:
+ statusLayout.setVisibility(View.VISIBLE);
+ progressBar.setVisibility(View.GONE);
+ statusView.setVisibility(View.VISIBLE);
+ statusView.setVisibility(View.GONE);
+ errorView.setVisibility(View.GONE);
+ break;
+ case ChatConst.SENDERROR:
+ statusLayout.setVisibility(View.VISIBLE);
+ progressBar.setVisibility(View.GONE);
+ statusView.setVisibility(View.GONE);
+ errorView.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ public void showTimeView(boolean isShow) {
+ timeView.setVisibility(isShow ? View.VISIBLE : View.GONE);
+ }
+
+ public void showUserName(boolean isShow) {
+ nameView.setVisibility(isShow ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java
new file mode 100644
index 0000000..a6938ea
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java
@@ -0,0 +1,65 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.administrator.chatdemo.ImageBrowserActivity;
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.utils.Constants;
+import com.example.administrator.chatdemo.utils.PhotoUtils;
+
+import io.github.leibnik.chatimageview.ChatImageView;
+
+
+/**
+ * Created by wli on 15/9/17.
+ */
+public class ChatItemImageHolder extends ChatItemHolder {
+
+ protected ChatImageView contentView;
+
+ public ChatItemImageHolder(Context context, ViewGroup root, boolean isLeft) {
+ super(context, root, isLeft);
+ }
+
+ @Override
+ public void initView() {
+ super.initView();
+ if (isLeft) {
+// contentView.setBackgroundResource(R.drawable.rc_ic_bubble_left);
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_image_layout, null));
+ } else {
+// contentView.setBackgroundResource(R.drawable.rc_ic_bubble_right);
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_image_layout, null));
+ }
+ contentView = (ChatImageView) itemView.findViewById(R.id.chat_item_image_view);
+
+ contentView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getContext(), ImageBrowserActivity.class);
+ intent.putExtra(Constants.IMAGE_LOCAL_PATH, message.getImageLocal());
+ intent.putExtra(Constants.IMAGE_URL, message.getImageUrl());
+ getContext().startActivity(intent);
+ }
+ });
+ }
+
+ @Override
+ public void bindData(Object o) {
+ super.bindData(o);
+// contentView.setImageResource(0);
+ ChatMessageBean message = (ChatMessageBean) o;
+ if (message != null) {
+ String localFilePath = message.getImageLocal();
+// if (!TextUtils.isEmpty(localFilePath)) {
+// ImageLoader.getInstance().displayImage("file://" + localFilePath, contentView);
+// } else {
+ PhotoUtils.displayImageCacheElseNetwork(contentView, localFilePath, message.getImageUrl());
+// }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java
new file mode 100644
index 0000000..2d30ba9
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java
@@ -0,0 +1,45 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+import com.example.administrator.chatdemo.utils.EmotionHelper;
+
+
+/**
+ * Created by wli on 15/9/17.
+ */
+public class ChatItemTextHolder extends ChatItemHolder {
+
+ protected TextView contentView;
+
+ public ChatItemTextHolder(Context context, ViewGroup root, boolean isLeft) {
+ super(context, root, isLeft);
+ }
+
+ @Override
+ public void initView() {
+ super.initView();
+ if (isLeft) {
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_left_text_layout, null));
+ contentView = (TextView) itemView.findViewById(R.id.chat_left_text_tv_content);
+ } else {
+ conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_text_layout, null));
+ contentView = (TextView) itemView.findViewById(R.id.chat_right_text_tv_content);
+ }
+ }
+
+ @Override
+ public void bindData(Object o) {
+ super.bindData(o);
+ ChatMessageBean message = (ChatMessageBean) o;
+ if (message != null) {
+// ChatMessageBean textMessage = (ChatMessageBean) message;
+ contentView.setText(EmotionHelper.replace(getContext(), message.getUserContent()));
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java
new file mode 100644
index 0000000..8ccb180
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java
@@ -0,0 +1,45 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+
+/**
+ * Created by wli on 15/8/25.
+ * RecyclerView.ViewHolder 的公共类,做了一些封装。目的:
+ * ViewHolder 与 Adapter 解耦,和 ViewHolder 相关的逻辑都放到 ViewHolder 里边,避免 Adapter 有相关逻辑
+ */
+
+public abstract class CommonViewHolder extends RecyclerView.ViewHolder {
+
+ public CommonViewHolder(Context context, ViewGroup root, int layoutRes) {
+ super(LayoutInflater.from(context).inflate(layoutRes, root, false));
+ }
+
+ public Context getContext() {
+ return itemView.getContext();
+ }
+
+ /**
+ * 用给定的 data 对 holder 的 view 进行赋值
+ */
+ public abstract void bindData(T t);
+
+ public void setData(T t) {
+ bindData(t);
+ }
+
+ /**
+ * 因为 CommonListAdapter 里边无法对于未知类型的 Class 进行实例化
+ * 所以需要如果想用 CommonListAdapter,必须要在对应的 CommonViewHolder 实例化一个 HOLDER_CREATOR
+ * 注意:public static ViewHolderCreator HOLDER_CREATOR,名字与修饰符都不能更改,否则有可能引发失败
+ * 具体样例可参见 DiscoverItemHolder
+ * 如果不使用 CommonListAdapter,则不需要实例化 ViewHolderCreator
+ * @param
+ */
+ public interface ViewHolderCreator {
+ public VH createByViewGroupAndType(ViewGroup parent, int viewType);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java
new file mode 100644
index 0000000..b87a474
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java
@@ -0,0 +1,23 @@
+package com.example.administrator.chatdemo.viewholder;
+
+import android.util.SparseArray;
+import android.view.View;
+
+/**
+ * Created by lzw on 14-9-21.
+ */
+public class ViewHolder {
+ public static T findViewById(View view, int id) {
+ SparseArray viewHolder = (SparseArray) view.getTag();
+ if (viewHolder == null) {
+ viewHolder = new SparseArray<>();
+ view.setTag(viewHolder);
+ }
+ View childView = viewHolder.get(id);
+ if (childView == null) {
+ childView = view.findViewById(id);
+ viewHolder.put(id, childView);
+ }
+ return (T) childView;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java b/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java
new file mode 100644
index 0000000..21fd9af
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java
@@ -0,0 +1,339 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.utils.AudioManager;
+import com.example.administrator.chatdemo.utils.PathUtils;
+
+import java.io.File;
+import java.math.BigDecimal;
+
+public class AudioRecordButton extends Button implements AudioManager.AudioStageListener {
+ private static final int STATE_NORMAL = 1;
+ private static final int STATE_RECORDING = 2;
+ private static final int STATE_WANT_TO_CANCEL = 3;
+ private static final int DISTANCE_Y_CANCEL = 50;
+ private static final int OVERTIME = 60;
+ private int mCurrentState = STATE_NORMAL;
+ // 已经开始录音
+ private boolean isRecording = false;
+ private DialogManager mDialogManager;
+ private float mTime = 0;
+ // 是否触发了onlongclick,准备好了
+ private boolean mReady;
+ private AudioManager mAudioManager;
+ private String saveDir = PathUtils.getSaveVoicePath(getContext());
+
+ private Handler mp3handler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO Auto-generated method stub
+ switch (msg.what) {
+ case AudioManager.MSG_ERROR_AUDIO_RECORD:
+ Toast.makeText(getContext(), "录音权限被屏蔽或者录音设备损坏!\n请在设置中检查是否开启权限!",
+ Toast.LENGTH_SHORT).show();
+ mDialogManager.dimissDialog();
+ mAudioManager.cancel();
+ reset();
+ break;
+ default:
+ break;
+ }
+ }
+
+ };
+
+ /**
+ * 先实现两个参数的构造方法,布局会默认引用这个构造方法, 用一个 构造参数的构造方法来引用这个方法 * @param context
+ */
+
+ public AudioRecordButton(Context context) {
+ this(context, null);
+ // TODO Auto-generated constructor stub
+ }
+
+ public AudioRecordButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mDialogManager = new DialogManager(getContext());
+//
+// try {
+// FileSaveUtil.createSDDirectory(FileSaveUtil.voice_dir);
+// } catch (IOException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+ mAudioManager = AudioManager.getInstance(PathUtils.getSaveVoicePath(getContext()));
+ mAudioManager.setOnAudioStageListener(this);
+ mAudioManager.setHandle(mp3handler);
+ setOnLongClickListener(new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ // TODO Auto-generated method
+// try {
+// FileSaveUtil.createSDDirectory(saveDir);
+// } catch (IOException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+ mAudioManager.setVocDir(saveDir);
+ mListener.onStart();
+ mReady = true;
+ mAudioManager.prepareAudio();
+ return false;
+ }
+ });
+ // TODO Auto-generated constructor stub
+ }
+
+ public void setSaveDir(String saveDir) {
+ this.saveDir = saveDir + saveDir;
+ }
+
+ /**
+ * 录音完成后的回调,回调给activiy,可以获得mtime和文件的路径
+ */
+ public interface AudioFinishRecorderListener {
+ void onStart();
+
+ void onFinished(float seconds, String filePath);
+ }
+
+ private AudioFinishRecorderListener mListener;
+
+ public void setAudioFinishRecorderListener(
+ AudioFinishRecorderListener listener) {
+ mListener = listener;
+ }
+
+ // 获取音量大小的runnable
+ private Runnable mGetVoiceLevelRunnable = new Runnable() {
+
+ @Override
+ public void run() {
+ // TODO Auto-generated method stub
+ while (isRecording) {
+ try {
+ Thread.sleep(100);
+ mTime += 0.1f;
+ mhandler.sendEmptyMessage(MSG_VOICE_CHANGE);
+ if (mTime >= OVERTIME) {
+ mTime = 60;
+ mhandler.sendEmptyMessage(MSG_OVERTIME_SEND);
+ isRecording = false;
+ break;
+ }
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ // 准备三个常量
+ private static final int MSG_AUDIO_PREPARED = 0X110;
+ private static final int MSG_VOICE_CHANGE = 0X111;
+ private static final int MSG_DIALOG_DIMISS = 0X112;
+ private static final int MSG_OVERTIME_SEND = 0X113;
+
+ private Handler mhandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_AUDIO_PREPARED:
+ // 显示应该是在audio end prepare之后回调
+ if (isTouch) {
+ mTime = 0;
+ mDialogManager.showRecordingDialog();
+ isRecording = true;
+ new Thread(mGetVoiceLevelRunnable).start();
+ }
+ // 需要开启一个线程来变换音量
+ break;
+ case MSG_VOICE_CHANGE:
+ mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(3));
+ break;
+ case MSG_DIALOG_DIMISS:
+ isRecording = false;
+ mDialogManager.dimissDialog();
+ break;
+ case MSG_OVERTIME_SEND:
+ mDialogManager.tooLong();
+ mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持续1.3s
+ if (mListener != null) {// 并且callbackActivity,保存录音
+ File file = new File(mAudioManager.getCurrentFilePath());
+ if (PathUtils.isFileExists(file)) {
+ mListener.onFinished(mTime,
+ mAudioManager.getCurrentFilePath());
+ } else {
+ mp3handler.sendEmptyMessage(AudioManager.MSG_ERROR_AUDIO_RECORD);
+ }
+ }
+ isRecording = false;
+ reset();// 恢复标志位
+ break;
+ }
+ }
+
+ ;
+ };
+
+ // 在这里面发送一个handler的消息
+ @Override
+ public void wellPrepared() {
+ // TODO Auto-generated method stub
+ mhandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
+ }
+
+ /**
+ * 直接复写这个监听函数
+ */
+ private boolean isTouch = false;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // TODO Auto-generated method stub
+ int action = event.getAction();
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ isTouch = true;
+ changeState(STATE_RECORDING);
+ break;
+ case MotionEvent.ACTION_MOVE:
+
+ if (isRecording) {
+
+ // 根据x,y来判断用户是否想要取消
+ if (wantToCancel(x, y)) {
+ changeState(STATE_WANT_TO_CANCEL);
+ } else {
+ changeState(STATE_RECORDING);
+ }
+
+ }
+
+ break;
+ case MotionEvent.ACTION_UP:
+ // 首先判断是否有触发onlongclick事件,没有的话直接返回reset
+ isTouch = false;
+ if (!mReady) {
+ reset();
+ return super.onTouchEvent(event);
+ }
+ // 如果按的时间太短,还没准备好或者时间录制太短,就离开了,则显示这个dialog
+ if (!isRecording || mTime < 0.6f) {
+ mDialogManager.tooShort();
+ mAudioManager.cancel();
+ mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持续1.3s
+ } else if (mCurrentState == STATE_RECORDING) {// 正常录制结束
+ mDialogManager.dimissDialog();
+ mAudioManager.release();// release释放一个mediarecorder
+ if (mListener != null) {// 并且callbackActivity,保存录音
+ BigDecimal b = new BigDecimal(mTime);
+ float f1 = b.setScale(1, BigDecimal.ROUND_HALF_UP)
+ .floatValue();
+ String currentFilePath = mAudioManager.getCurrentFilePath();
+ Log.i("AudioRecordButton", "filePath:" + currentFilePath);
+ File file = new File(currentFilePath);
+ if (PathUtils.isFileExists(file)) {
+ mListener.onFinished(f1, currentFilePath);
+ } else {
+ mp3handler.sendEmptyMessage(AudioManager.MSG_ERROR_AUDIO_RECORD);
+ }
+ }
+ } else if (mCurrentState == STATE_WANT_TO_CANCEL) {
+ mAudioManager.cancel();
+ mDialogManager.dimissDialog();
+ }
+ isRecording = false;
+ reset();// 恢复标志位
+
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ isTouch = false;
+ reset();
+ break;
+
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * 回复标志位以及状态
+ */
+ private void reset() {
+ // TODO Auto-generated method stub
+ isRecording = false;
+ changeState(STATE_NORMAL);
+ mReady = false;
+ mTime = 0;
+ }
+
+ private boolean wantToCancel(int x, int y) {
+ // TODO Auto-generated method stub
+
+ if (x < 0 || x > getWidth()) {// 判断是否在左边,右边,上边,下边
+ return true;
+ }
+ if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void changeState(int state) {
+ // TODO Auto-generated method stub
+ if (mCurrentState != state) {
+ mCurrentState = state;
+ switch (mCurrentState) {
+ case STATE_NORMAL:
+ setBackgroundResource(R.drawable.button_recordnormal);
+ setText(R.string.normal);
+
+ break;
+ case STATE_RECORDING:
+ setBackgroundResource(R.drawable.button_recording);
+ setText(R.string.recording);
+ if (isRecording) {
+ mDialogManager.recording();
+ // 复写dialog.recording();
+ }
+ break;
+
+ case STATE_WANT_TO_CANCEL:
+ setBackgroundResource(R.drawable.button_recording);
+ setText(R.string.want_to_cancle);
+ // dialog want to cancel
+ mDialogManager.wantToCancel();
+ break;
+
+ }
+ }
+
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java b/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java
new file mode 100644
index 0000000..d5ed323
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java
@@ -0,0 +1,58 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.example.administrator.chatdemo.R;
+
+
+public class ChatBottomView extends LinearLayout{
+ private View baseView;
+ private LinearLayout imageGroup;
+ private LinearLayout cameraGroup;
+ private HeadIconSelectorView.OnHeadIconClickListener onHeadIconClickListener;
+ public static final int FROM_CAMERA = 1;
+ public static final int FROM_GALLERY = 2;
+ public ChatBottomView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ // TODO Auto-generated constructor stub
+ findView();
+ init();
+ }
+
+ private void findView(){
+ baseView = LayoutInflater.from(getContext()).inflate(R.layout.layout_tongbaobottom, this);
+ imageGroup = (LinearLayout) baseView.findViewById(R.id.image_bottom_group);
+ cameraGroup = (LinearLayout) baseView.findViewById(R.id.camera_group);
+ }
+ private void init(){
+ cameraGroup.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(FROM_CAMERA);
+ }
+ }
+ });
+ imageGroup.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(FROM_GALLERY);
+ }
+ }
+ });
+ }
+
+ public void setOnHeadIconClickListener(
+ HeadIconSelectorView.OnHeadIconClickListener onHeadIconClickListener) {
+ // TODO Auto-generated method stub
+ this.onHeadIconClickListener = onHeadIconClickListener;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java b/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java
new file mode 100644
index 0000000..82bdd11
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java
@@ -0,0 +1,304 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.os.Build;
+import android.support.annotation.AnimatorRes;
+import android.support.annotation.DrawableRes;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.widget.LinearLayout;
+
+import com.example.administrator.chatdemo.R;
+
+import static android.support.v4.view.ViewPager.OnPageChangeListener;
+
+public class CircleIndicator extends LinearLayout {
+
+ private final static int DEFAULT_INDICATOR_WIDTH = 5;
+ private ViewPager mViewpager;
+ private int mIndicatorMargin = -1;
+ private int mIndicatorWidth = -1;
+ private int mIndicatorHeight = -1;
+ private int mAnimatorResId = R.anim.scale_with_alpha;
+ private int mAnimatorReverseResId = 0;
+ private int mIndicatorBackgroundResId = R.drawable.white_radius;
+ private int mIndicatorUnselectedBackgroundResId = R.drawable.white_radius;
+ private Animator mAnimatorOut;
+ private Animator mAnimatorIn;
+ private Animator mImmediateAnimatorOut;
+ private Animator mImmediateAnimatorIn;
+
+ private int mLastPosition = -1;
+
+ public CircleIndicator(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public CircleIndicator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ handleTypedArray(context, attrs);
+ checkIndicatorConfig(context);
+ }
+
+ private void handleTypedArray(Context context, AttributeSet attrs) {
+ if (attrs == null) {
+ return;
+ }
+
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicator);
+ mIndicatorWidth =
+ typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_width, -1);
+ mIndicatorHeight =
+ typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_height, -1);
+ mIndicatorMargin =
+ typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_margin, -1);
+
+ mAnimatorResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator,
+ R.anim.scale_with_alpha);
+ mAnimatorReverseResId =
+ typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator_reverse, 0);
+ mIndicatorBackgroundResId =
+ typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable,
+ R.drawable.white_radius);
+ mIndicatorUnselectedBackgroundResId =
+ typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable_unselected,
+ mIndicatorBackgroundResId);
+
+ int orientation = typedArray.getInt(R.styleable.CircleIndicator_orientation, -1);
+ setOrientation(orientation == VERTICAL ? VERTICAL : HORIZONTAL);
+
+ int gravity = typedArray.getInt(R.styleable.CircleIndicator_gravity_ci, -1);
+ setGravity(gravity >= 0 ? gravity : Gravity.CENTER);
+
+ typedArray.recycle();
+ }
+
+ /**
+ * Create and configure Indicator in Java code.
+ */
+ public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin) {
+ configureIndicator(indicatorWidth, indicatorHeight, indicatorMargin,
+ R.anim.scale_with_alpha, 0, R.drawable.white_radius, R.drawable.white_radius);
+ }
+
+ public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin,
+ @AnimatorRes int animatorId, @AnimatorRes int animatorReverseId,
+ @DrawableRes int indicatorBackgroundId,
+ @DrawableRes int indicatorUnselectedBackgroundId) {
+
+ mIndicatorWidth = indicatorWidth;
+ mIndicatorHeight = indicatorHeight;
+ mIndicatorMargin = indicatorMargin;
+
+ mAnimatorResId = animatorId;
+ mAnimatorReverseResId = animatorReverseId;
+ mIndicatorBackgroundResId = indicatorBackgroundId;
+ mIndicatorUnselectedBackgroundResId = indicatorUnselectedBackgroundId;
+
+ checkIndicatorConfig(getContext());
+ }
+
+ private void checkIndicatorConfig(Context context) {
+ mIndicatorWidth = (mIndicatorWidth < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorWidth;
+ mIndicatorHeight =
+ (mIndicatorHeight < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorHeight;
+ mIndicatorMargin =
+ (mIndicatorMargin < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorMargin;
+
+ mAnimatorResId = (mAnimatorResId == 0) ? R.anim.scale_with_alpha : mAnimatorResId;
+
+ mAnimatorOut = createAnimatorOut(context);
+ mImmediateAnimatorOut = createAnimatorOut(context);
+ mImmediateAnimatorOut.setDuration(0);
+
+ mAnimatorIn = createAnimatorIn(context);
+ mImmediateAnimatorIn = createAnimatorIn(context);
+ mImmediateAnimatorIn.setDuration(0);
+
+ mIndicatorBackgroundResId = (mIndicatorBackgroundResId == 0) ? R.drawable.white_radius
+ : mIndicatorBackgroundResId;
+ mIndicatorUnselectedBackgroundResId =
+ (mIndicatorUnselectedBackgroundResId == 0) ? mIndicatorBackgroundResId
+ : mIndicatorUnselectedBackgroundResId;
+ }
+
+ private Animator createAnimatorOut(Context context) {
+ return AnimatorInflater.loadAnimator(context, mAnimatorResId);
+ }
+
+ private Animator createAnimatorIn(Context context) {
+ Animator animatorIn;
+ if (mAnimatorReverseResId == 0) {
+ animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorResId);
+ animatorIn.setInterpolator(new ReverseInterpolator());
+ } else {
+ animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorReverseResId);
+ }
+ return animatorIn;
+ }
+
+ public void setViewPager(ViewPager viewPager) {
+ mViewpager = viewPager;
+ if (mViewpager != null && mViewpager.getAdapter() != null) {
+ mLastPosition = -1;
+ createIndicators();
+ mViewpager.removeOnPageChangeListener(mInternalPageChangeListener);
+ mViewpager.addOnPageChangeListener(mInternalPageChangeListener);
+ mInternalPageChangeListener.onPageSelected(mViewpager.getCurrentItem());
+ }
+ }
+
+ private final OnPageChangeListener mInternalPageChangeListener = new OnPageChangeListener() {
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+
+ if (mViewpager.getAdapter() == null || mViewpager.getAdapter().getCount() <= 0) {
+ return;
+ }
+
+ if (mAnimatorIn.isRunning()) {
+ mAnimatorIn.end();
+ mAnimatorIn.cancel();
+ }
+
+ if (mAnimatorOut.isRunning()) {
+ mAnimatorOut.end();
+ mAnimatorOut.cancel();
+ }
+
+ View currentIndicator;
+ if (mLastPosition >= 0 && (currentIndicator = getChildAt(mLastPosition)) != null) {
+ currentIndicator.setBackgroundResource(mIndicatorUnselectedBackgroundResId);
+ mAnimatorIn.setTarget(currentIndicator);
+ mAnimatorIn.start();
+ }
+
+ View selectedIndicator = getChildAt(position);
+ if (selectedIndicator != null) {
+ selectedIndicator.setBackgroundResource(mIndicatorBackgroundResId);
+ mAnimatorOut.setTarget(selectedIndicator);
+ mAnimatorOut.start();
+ }
+ mLastPosition = position;
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ };
+
+ public DataSetObserver getDataSetObserver() {
+ return mInternalDataSetObserver;
+ }
+
+ private DataSetObserver mInternalDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ if (mViewpager == null) {
+ return;
+ }
+
+ int newCount = mViewpager.getAdapter().getCount();
+ int currentCount = getChildCount();
+
+ if (newCount == currentCount) { // No change
+ return;
+ } else if (mLastPosition < newCount) {
+ mLastPosition = mViewpager.getCurrentItem();
+ } else {
+ mLastPosition = -1;
+ }
+
+ createIndicators();
+ }
+ };
+
+ /**
+ * @deprecated User ViewPager addOnPageChangeListener
+ */
+ @Deprecated
+ public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
+ if (mViewpager == null) {
+ throw new NullPointerException("can not find Viewpager , setViewPager first");
+ }
+ mViewpager.removeOnPageChangeListener(onPageChangeListener);
+ mViewpager.addOnPageChangeListener(onPageChangeListener);
+ }
+
+ private void createIndicators() {
+ removeAllViews();
+ int count = mViewpager.getAdapter().getCount();
+ if (count <= 0) {
+ return;
+ }
+ int currentItem = mViewpager.getCurrentItem();
+
+ for (int i = 0; i < count; i++) {
+ if (currentItem == i) {
+ addIndicator(mIndicatorBackgroundResId, mImmediateAnimatorOut);
+ } else {
+ addIndicator(mIndicatorUnselectedBackgroundResId, mImmediateAnimatorIn);
+ }
+ }
+ }
+
+ private void addIndicator(@DrawableRes int backgroundDrawableId, Animator animator) {
+ if (animator.isRunning()) {
+ animator.end();
+ animator.cancel();
+ }
+
+ View Indicator = new View(getContext());
+ Indicator.setBackgroundResource(backgroundDrawableId);
+ addView(Indicator, mIndicatorWidth, mIndicatorHeight);
+ LayoutParams lp = (LayoutParams) Indicator.getLayoutParams();
+ lp.leftMargin = mIndicatorMargin;
+ lp.rightMargin = mIndicatorMargin;
+ Indicator.setLayoutParams(lp);
+
+ animator.setTarget(Indicator);
+ animator.start();
+ }
+
+ private class ReverseInterpolator implements Interpolator {
+ @Override
+ public float getInterpolation(float value) {
+ return Math.abs(1.0f - value);
+ }
+ }
+
+ public int dip2px(float dpValue) {
+ final float scale = getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java b/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java
new file mode 100644
index 0000000..0ee367f
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java
@@ -0,0 +1,149 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.utils.ScreenUtil;
+
+
+@SuppressLint("InflateParams")
+public class DialogManager {
+
+ /**
+ * 以下为dialog的初始化控件,包括其中的布局文件
+ */
+
+ private Dialog mDialog;
+
+ private ImageView mIcon;
+ private ImageView mVoice;
+
+ private TextView mLable;
+
+ private Context mContext;
+
+ public DialogManager(Context context) {
+ // TODO Auto-generated constructor stub
+ mContext = context;
+ }
+
+ public void showRecordingDialog() {
+ // TODO Auto-generated method stub
+
+ mDialog = new Dialog(mContext, R.style.Theme_audioDialog);
+ mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ // 用layoutinflater来引用布局
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = inflater.inflate(R.layout.layout_voice_dialog_manager, null);
+ mDialog.setContentView(view);
+
+ mIcon = (ImageView) mDialog.findViewById(R.id.dialog_icon);
+ mVoice = (ImageView) mDialog.findViewById(R.id.dialog_voice);
+ mLable = (TextView) mDialog.findViewById(R.id.recorder_dialogtext);
+
+ Window dialogWindow = mDialog.getWindow();
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ int width = ScreenUtil.getScreenWidth(mContext) / 2;
+ lp.width = width; // 宽度
+ lp.height = width; // 高度
+ dialogWindow.setAttributes(lp);
+ mDialog.setCancelable(false);
+ mDialog.show();
+
+ }
+
+ /**
+ * 设置正在录音时的dialog界面
+ */
+ public void recording() {
+ if (mDialog != null && mDialog.isShowing()) {
+ mIcon.setVisibility(View.GONE);
+ mVoice.setVisibility(View.VISIBLE);
+ mLable.setVisibility(View.VISIBLE);
+ mLable.setText(R.string.shouzhishanghua);
+ }
+ }
+
+ /**
+ * 取消界面
+ */
+ public void wantToCancel() {
+ // TODO Auto-generated method stub
+ if (mDialog != null && mDialog.isShowing()) {
+ mIcon.setVisibility(View.VISIBLE);
+ mVoice.setVisibility(View.GONE);
+ mLable.setVisibility(View.VISIBLE);
+
+ mIcon.setImageResource(R.mipmap.cancel);
+ mLable.setText(R.string.want_to_cancle);
+ }
+
+ }
+
+ // 时间过短
+ public void tooShort() {
+ // TODO Auto-generated method stub
+ if (mDialog != null && mDialog.isShowing()) {
+ mIcon.setVisibility(View.VISIBLE);
+ mVoice.setVisibility(View.GONE);
+ mLable.setVisibility(View.VISIBLE);
+
+ mIcon.setImageResource(R.mipmap.voice_to_short);
+ mLable.setText(R.string.tooshort);
+ }
+
+ }
+ // 时间过长
+ public void tooLong() {
+ // TODO Auto-generated method stub
+ if (mDialog != null && mDialog.isShowing()) {
+ mIcon.setVisibility(View.VISIBLE);
+ mVoice.setVisibility(View.GONE);
+ mLable.setVisibility(View.VISIBLE);
+
+ mIcon.setImageResource(R.mipmap.voice_to_short);
+ mLable.setText(R.string.toolong);
+ }
+
+ }
+ // 隐藏dialog
+ public void dimissDialog() {
+ // TODO Auto-generated method stub
+
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+
+ }
+
+ public void updateVoiceLevel(int level) {
+ // TODO Auto-generated method stub
+
+ if (mDialog != null && mDialog.isShowing()) {
+ int resId;
+ if(level >= 1 && level < 2){
+ resId = mContext.getResources().getIdentifier("tb_voice1",
+ "mipmap", mContext.getPackageName());
+ }else if(level >= 2 && level < 3){
+ resId = mContext.getResources().getIdentifier("tb_voice2",
+ "mipmap", mContext.getPackageName());
+ }else{
+ resId = mContext.getResources().getIdentifier("tb_voice3",
+ "mipmap", mContext.getPackageName());
+ }
+ mVoice.setImageResource(resId);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java b/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java
new file mode 100644
index 0000000..d3fdd95
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java
@@ -0,0 +1,33 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+import com.example.administrator.chatdemo.utils.EmotionHelper;
+
+
+public class EmotionEditText extends EditText {
+
+ public EmotionEditText(Context context) {
+ super(context);
+ }
+
+ public EmotionEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public EmotionEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ if (!TextUtils.isEmpty(text)) {
+ super.setText(EmotionHelper.replace(getContext(), text.toString()), type);
+ } else {
+ super.setText(text, type);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java b/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java
new file mode 100644
index 0000000..3762bd5
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java
@@ -0,0 +1,331 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.ScaleAnimation;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.example.administrator.chatdemo.R;
+
+
+public class HeadIconSelectorView extends RelativeLayout implements
+ GestureDetector.OnGestureListener {
+
+ private View baseView;
+
+ private RelativeLayout mainRl;
+ private LinearLayout bottomLl;
+ private LinearLayout cameraLl;
+ private LinearLayout galleryLl;
+ private LinearLayout cancelLl;
+
+ private GestureDetector gestureDetector; // 手势检测器
+ private boolean isAnimationing = false;
+ private OnHeadIconClickListener onHeadIconClickListener;
+ public static final int FROM_CAMERA = 2;
+ public static final int FROM_GALLERY = 3;
+ public static final int CANCEL = 4;
+ public static final int BLANK_CANCEL = 5;
+
+ public HeadIconSelectorView(Context context) {
+ super(context);
+ init();
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void init() {
+ findView();
+ this.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return gestureDetector.onTouchEvent(event);
+ }
+ });
+ bottomLl.setVisibility(View.INVISIBLE);
+ mainRl.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(BLANK_CANCEL);
+ }
+ cancel();
+ }
+ });
+ cancelLl.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(CANCEL);
+ }
+ cancel();
+ }
+ });
+ cameraLl.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(FROM_CAMERA);
+ }
+ cancel();
+ }
+ });
+ galleryLl.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ if (null != onHeadIconClickListener) {
+ onHeadIconClickListener.onClick(FROM_GALLERY);
+ }
+ cancel();
+ }
+ });
+ }
+
+ @SuppressWarnings("deprecation")
+ private void findView() {
+ gestureDetector = new GestureDetector(this);
+ baseView = LayoutInflater.from(getContext()).inflate(
+ R.layout.layout_view_headicon, this);
+ mainRl = (RelativeLayout) baseView.findViewById(R.id.head_icon_main_rl);
+ bottomLl = (LinearLayout) baseView.findViewById(R.id.head_icon_main_ll);
+ cameraLl = (LinearLayout) baseView
+ .findViewById(R.id.head_icon_camera_ll);
+ galleryLl = (LinearLayout) baseView
+ .findViewById(R.id.head_icon_gallery_ll);
+ cancelLl = (LinearLayout) baseView
+ .findViewById(R.id.head_icon_cancel_ll);
+ }
+
+ protected void bottomViewFlyIn() {
+ final ScaleAnimation sa1 = new ScaleAnimation(1, 1, 0, 1.2f,
+ Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f);
+ sa1.setDuration(250);
+ final ScaleAnimation sa2 = new ScaleAnimation(1, 1, 1.2f, 1,
+ Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f);
+ sa2.setDuration(150);
+ sa1.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ isAnimationing = true;
+ bottomLl.setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ isAnimationing = false;
+ bottomLl.startAnimation(sa2);
+ }
+ });
+ bottomLl.startAnimation(sa1);
+ }
+
+ protected void bottomViewFlyOut() {
+ final ScaleAnimation sa1 = new ScaleAnimation(1, 1, 1, 1.2f,
+ Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f);
+ sa1.setDuration(150);
+ final ScaleAnimation sa2 = new ScaleAnimation(1, 1, 1.2f, 0,
+ Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f);
+ sa2.setDuration(250);
+ sa1.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ isAnimationing = true;
+ bottomLl.setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ bottomLl.startAnimation(sa2);
+ }
+ });
+ sa2.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ isAnimationing = false;
+ bottomLl.setVisibility(INVISIBLE);
+ flyOut();
+ }
+ });
+ bottomLl.startAnimation(sa1);
+ }
+
+ protected void cancel() {
+ if (!isAnimationing) {
+ if (bottomLl.getVisibility() == VISIBLE) {
+ bottomViewFlyOut();
+ return;
+ }
+ }
+ }
+
+ public void flyIn() {
+ AlphaAnimation aa = new AlphaAnimation(0, 1);
+ aa.setDuration(300);
+ aa.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ isAnimationing = true;
+ setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ isAnimationing = false;
+ bottomViewFlyIn();
+ }
+ });
+ startAnimation(aa);
+ }
+
+ public void flyOut() {
+ AlphaAnimation aa = new AlphaAnimation(1, 0);
+ aa.setDuration(300);
+ aa.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ isAnimationing = true;
+ setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ isAnimationing = false;
+ destroy();
+ }
+ });
+ startAnimation(aa);
+ }
+
+ protected void destroy() {
+ if (this.getParent() != null) {
+ ((ViewGroup) this.getParent()).removeView(this);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (!isAnimationing) {
+ if (bottomLl.getVisibility() == VISIBLE) {
+ bottomViewFlyOut();
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ public OnHeadIconClickListener getOnHeadIconClickListener() {
+ return onHeadIconClickListener;
+ }
+
+ public void setOnHeadIconClickListener(
+ OnHeadIconClickListener onHeadIconClickListener) {
+ this.onHeadIconClickListener = onHeadIconClickListener;
+ }
+
+ public interface OnHeadIconClickListener {
+ public void onClick(int from);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return gestureDetector.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ // TODO Auto-generated method stub
+
+ }
+
+ private float minVelocityY = 100f;// 10个像素每秒
+ private float minDistanceY = 100f;// 100个像素
+
+ // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN,
+ // 多个ACTION_MOVE, 1个ACTION_UP触发
+ // e1:第1个ACTION_DOWN MotionEvent
+ // e2:最后一个ACTION_MOVE MotionEvent
+ // velocityX:X轴上的移动速度,像素/秒
+ // velocityY:Y轴上的移动速度,像素/秒
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ // 手势从上到下且移动速度较快
+ if (e2.getY() - e1.getY() > minDistanceY && velocityY > minVelocityY) {
+ cancel();
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java b/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java
new file mode 100644
index 0000000..abfde57
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java
@@ -0,0 +1,315 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.adapter.ChatEmotionGridAdapter;
+import com.example.administrator.chatdemo.adapter.ChatEmotionPagerAdapter;
+import com.example.administrator.chatdemo.utils.EmotionHelper;
+import com.example.administrator.chatdemo.utils.KeyBoardUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by dwq on 2017/7/19/019.
+ * e-mail:lomapa@163.com
+ */
+
+public class InputBarLayout extends LinearLayout {
+ private EditText mEditTextContent;
+ private ImageView ivMore;
+ private ImageView ivEmoji;
+ private ImageView ivVoice;
+ private ViewPager emotionPager;
+ private AudioRecordButton voiceBtn;
+ private LinearLayout llyEmojiGroup;
+ private TextView tvSend;
+ private ChatBottomView cbvOther;
+
+ private CircleIndicator ciBanner;
+
+ private OnMessageSendListener onMessageSendListener;
+
+ private OnBottomIconClickListener onBottomIconClickListener;
+
+ public void setOnMessageSendListener(OnMessageSendListener onMessageSendListener) {
+ this.onMessageSendListener = onMessageSendListener;
+ }
+
+ public void setOnBottomIconClickListener(OnBottomIconClickListener onBottomIconClickListener) {
+ this.onBottomIconClickListener = onBottomIconClickListener;
+ }
+
+ public InputBarLayout(Context context) {
+ super(context);
+ initView(context);
+ }
+
+ public InputBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initView(context);
+ }
+
+ public InputBarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView(context);
+ }
+
+ private void initView(Context context) {
+ View.inflate(context, R.layout.chat_input_bottom_bar_layout, this);
+ mEditTextContent = (EditText) findViewById(R.id.et_msg);
+ ivMore = (ImageView) findViewById(R.id.iv_more);
+ ivEmoji = (ImageView) findViewById(R.id.iv_emoji);
+ ivVoice = (ImageView) findViewById(R.id.iv_voice);
+ emotionPager = (ViewPager) findViewById(R.id.vPager);
+ voiceBtn = (AudioRecordButton) findViewById(R.id.voice_btn);
+ llyEmojiGroup = (LinearLayout) findViewById(R.id.lly_emoji_group);
+ tvSend = (TextView) findViewById(R.id.tv_send);
+ cbvOther = (ChatBottomView) findViewById(R.id.cbv_other);
+ ciBanner = (CircleIndicator) findViewById(R.id.ci_banner);
+
+ initListener(context);
+ }
+
+ private void initListener(final Context context) {
+ mEditTextContent.setOnKeyListener(onKeyListener);
+
+ ivVoice.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ // TODO Auto-generated method stub
+ if (voiceBtn.getVisibility() == View.GONE) {
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ ivMore.setBackgroundResource(R.mipmap.ic_btn_more);
+ mEditTextContent.setVisibility(View.GONE);
+ llyEmojiGroup.setVisibility(View.GONE);
+ cbvOther.setVisibility(View.GONE);
+ voiceBtn.setVisibility(View.VISIBLE);
+ KeyBoardUtils.hideKeyBoard(context,
+ mEditTextContent);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_keybroad);
+ } else {
+ mEditTextContent.setVisibility(View.VISIBLE);
+ voiceBtn.setVisibility(View.GONE);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice);
+ KeyBoardUtils.showKeyBoard(context, mEditTextContent);
+ }
+ }
+
+ });
+
+ ivMore.setOnClickListener(new View.OnClickListener() {
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ llyEmojiGroup.setVisibility(View.GONE);
+ if (cbvOther.getVisibility() == View.GONE
+ ) {
+ mEditTextContent.setVisibility(View.VISIBLE);
+ ivMore.setFocusable(true);
+ voiceBtn.setVisibility(View.GONE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice);
+ cbvOther.setVisibility(View.VISIBLE);
+ KeyBoardUtils.hideKeyBoard(context,
+ mEditTextContent);
+ } else {
+ cbvOther.setVisibility(View.GONE);
+ KeyBoardUtils.showKeyBoard(context, mEditTextContent);
+ }
+ }
+ });
+
+ tvSend.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // TODO 发送消息
+ String message = mEditTextContent.getText().toString().trim();
+ if (onMessageSendListener != null && !TextUtils.isEmpty(message)) {
+ onMessageSendListener.sendMessage(message);
+ mEditTextContent.setText("");
+ }
+ }
+ });
+
+ mEditTextContent.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // 收起部分布局
+ llyEmojiGroup.setVisibility(View.GONE);
+ cbvOther.setVisibility(View.GONE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice);
+ }
+
+ });
+
+ cbvOther.setOnHeadIconClickListener(new HeadIconSelectorView.OnHeadIconClickListener() {
+ @Override
+ public void onClick(int from) {
+ if (onBottomIconClickListener != null)
+ onBottomIconClickListener.onIconClickListener(from);
+
+ }
+ });
+
+
+ ivEmoji.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // 显示或收起表情列表
+ cbvOther.setVisibility(View.GONE);
+ if (llyEmojiGroup.getVisibility() == View.GONE) {
+ mEditTextContent.setVisibility(View.VISIBLE);
+ voiceBtn.setVisibility(View.GONE);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice);
+ llyEmojiGroup.setVisibility(View.VISIBLE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_keybroad);
+ KeyBoardUtils.hideKeyBoard(context,
+ mEditTextContent);
+ } else {
+ llyEmojiGroup.setVisibility(View.GONE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ KeyBoardUtils.showKeyBoard(context, mEditTextContent);
+ }
+ }
+ });
+
+ ivEmoji.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // 显示或收起表情列表
+ cbvOther.setVisibility(View.GONE);
+ if (llyEmojiGroup.getVisibility() == View.GONE) {
+ mEditTextContent.setVisibility(View.VISIBLE);
+ voiceBtn.setVisibility(View.GONE);
+ ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice);
+ llyEmojiGroup.setVisibility(View.VISIBLE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_keybroad);
+ KeyBoardUtils.hideKeyBoard(context,
+ mEditTextContent);
+ } else {
+ llyEmojiGroup.setVisibility(View.GONE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ KeyBoardUtils.showKeyBoard(context, mEditTextContent);
+ }
+ }
+ });
+
+ initEmotionPager();
+
+ voiceBtn.setAudioFinishRecorderListener(new AudioRecordButton.AudioFinishRecorderListener() {
+ @Override
+ public void onStart() {
+ if (onMessageSendListener != null)
+ onMessageSendListener.onVoiceRecordStart();
+ }
+
+ @Override
+ public void onFinished(float seconds, String filePath) {
+ if (onMessageSendListener != null)
+ onMessageSendListener.sendVoice(seconds, filePath);
+ }
+ });
+ }
+
+ private View.OnKeyListener onKeyListener = new View.OnKeyListener() {
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER
+ && event.getAction() == KeyEvent.ACTION_DOWN) {
+ // 发送消息
+ String message = mEditTextContent.getText().toString().trim();
+ if (onMessageSendListener != null && !TextUtils.isEmpty(message)) {
+ onMessageSendListener.sendMessage(message);
+ mEditTextContent.setText("");
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ /**
+ * 初始化 emotionPager
+ */
+ private void initEmotionPager() {
+ List views = new ArrayList();
+ for (int i = 0; i < EmotionHelper.emojiGroups.size(); i++) {
+ views.add(getEmotionGridView(i));
+ }
+ ChatEmotionPagerAdapter pagerAdapter = new ChatEmotionPagerAdapter(views);
+ emotionPager.setOffscreenPageLimit(4);
+ emotionPager.setAdapter(pagerAdapter);
+ ciBanner.setViewPager(emotionPager);
+ }
+
+ private View getEmotionGridView(int pos) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View emotionView = inflater.inflate(R.layout.chat_emotion_gridview, null, false);
+ GridView gridView = (GridView) emotionView.findViewById(R.id.gridview);
+ final ChatEmotionGridAdapter chatEmotionGridAdapter = new ChatEmotionGridAdapter(getContext());
+ List pageEmotions = EmotionHelper.emojiGroups.get(pos);
+ chatEmotionGridAdapter.setDatas(pageEmotions);
+ gridView.setAdapter(chatEmotionGridAdapter);
+ gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ String emotionText = (String) parent.getAdapter().getItem(position);
+ int start = mEditTextContent.getSelectionStart();
+ StringBuilder sb = new StringBuilder(mEditTextContent.getText());
+ sb.replace(mEditTextContent.getSelectionStart(), mEditTextContent.getSelectionEnd(), emotionText);
+ mEditTextContent.setText(sb.toString());
+
+ CharSequence info = mEditTextContent.getText();
+ if (info != null) {
+ Spannable spannable = (Spannable) info;
+ Selection.setSelection(spannable, start + emotionText.length());
+ }
+ }
+ });
+ return gridView;
+ }
+
+ public void hideBottomLayout() {
+ llyEmojiGroup.setVisibility(GONE);
+ ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji);
+ cbvOther.setVisibility(GONE);
+ }
+
+ public interface OnMessageSendListener {
+ void sendMessage(String message);
+
+ void sendVoice(float seconds, String filePath);
+
+ void onVoiceRecordStart();
+ }
+
+ public interface OnBottomIconClickListener {
+ void onIconClickListener(int from);
+ }
+}
diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java b/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java
new file mode 100644
index 0000000..05794c0
--- /dev/null
+++ b/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java
@@ -0,0 +1,83 @@
+package com.example.administrator.chatdemo.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.AnimationDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import com.example.administrator.chatdemo.R;
+import com.example.administrator.chatdemo.bean.ChatMessageBean;
+
+
+/**
+ * Created by lzw on 14-9-22.
+ */
+public class PlayButton extends TextView implements View.OnClickListener {
+ private String path;
+ private boolean leftSide;
+ private AnimationDrawable anim;
+
+
+ private ChatMessageBean messageBean;
+
+ public PlayButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ leftSide = getLeftFromAttrs(context, attrs);
+ setLeftSide(leftSide);
+ setOnClickListener(this);
+ }
+
+ public void setLeftSide(boolean leftSide) {
+ this.leftSide = leftSide;
+ stopRecordAnimation();
+ }
+
+ public boolean getLeftFromAttrs(Context context, AttributeSet attrs) {
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChatPlayBtn);
+ boolean left = true;
+ for (int i = 0; i < typedArray.getIndexCount(); i++) {
+ int attr = typedArray.getIndex(i);
+ if (attr == R.styleable.ChatPlayBtn_left) {
+ left = typedArray.getBoolean(attr, true);
+ }
+ }
+ return left;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public void setMessageBean(ChatMessageBean messageBean) {
+ this.messageBean = messageBean;
+ }
+
+ @Override
+ public void onClick(View v) {
+
+// new PlayButtonClickListener(this, leftSide, getContext(), path).onClick(this);
+ }
+
+ public void startRecordAnimation() {
+ Log.i("PlayButton", "startRecordAnimation" + leftSide);
+ setCompoundDrawablesWithIntrinsicBounds(leftSide ? R.drawable.chat_anim_voice_left : 0,
+ 0, !leftSide ? R.drawable.chat_anim_voice_right : 0, 0);
+ anim = (AnimationDrawable) getCompoundDrawables()[leftSide ? 0 : 2];
+ anim.start();
+ }
+
+ //
+ public void stopRecordAnimation() {
+ Log.i("PlayButton", "stopRecordAnimation" + leftSide);
+ setCompoundDrawablesWithIntrinsicBounds(leftSide ? R.drawable.chat_voice_right3 : 0,
+ 0, !leftSide ? R.drawable.chat_voice_left3 : 0, 0);
+ if (anim != null) {
+ anim.stop();
+ }
+ }
+
+
+}
diff --git a/app/src/main/res/anim/scale_with_alpha.xml b/app/src/main/res/anim/scale_with_alpha.xml
new file mode 100644
index 0000000..8ef7f85
--- /dev/null
+++ b/app/src/main/res/anim/scale_with_alpha.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png b/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png
new file mode 100644
index 0000000..25d57df
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png b/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png
new file mode 100644
index 0000000..750be72
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png b/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png
new file mode 100644
index 0000000..b328653
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png b/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png
new file mode 100644
index 0000000..82eba61
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png b/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png
new file mode 100644
index 0000000..21c1d2a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png b/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png
new file mode 100644
index 0000000..72c6d01
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png b/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png
new file mode 100644
index 0000000..a443809
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left.png b/app/src/main/res/drawable-xhdpi/chat_voice_left.png
new file mode 100644
index 0000000..2d7fd8f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left1.png b/app/src/main/res/drawable-xhdpi/chat_voice_left1.png
new file mode 100644
index 0000000..3a6d492
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left1.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left2.png b/app/src/main/res/drawable-xhdpi/chat_voice_left2.png
new file mode 100644
index 0000000..d3306f8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left2.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left3.png b/app/src/main/res/drawable-xhdpi/chat_voice_left3.png
new file mode 100644
index 0000000..28b1fb7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left3.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png b/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png
new file mode 100644
index 0000000..4a29e24
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right.png b/app/src/main/res/drawable-xhdpi/chat_voice_right.png
new file mode 100644
index 0000000..b173db3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right1.png b/app/src/main/res/drawable-xhdpi/chat_voice_right1.png
new file mode 100644
index 0000000..3160a0b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right1.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right2.png b/app/src/main/res/drawable-xhdpi/chat_voice_right2.png
new file mode 100644
index 0000000..286d579
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right2.png differ
diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right3.png b/app/src/main/res/drawable-xhdpi/chat_voice_right3.png
new file mode 100644
index 0000000..256cc20
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right3.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_angry.png b/app/src/main/res/drawable-xhdpi/emoji_angry.png
new file mode 100644
index 0000000..f95bfa8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_angry.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_anguished.png b/app/src/main/res/drawable-xhdpi/emoji_anguished.png
new file mode 100644
index 0000000..c625947
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_anguished.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_astonished.png b/app/src/main/res/drawable-xhdpi/emoji_astonished.png
new file mode 100644
index 0000000..858a834
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_astonished.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_blush.png b/app/src/main/res/drawable-xhdpi/emoji_blush.png
new file mode 100644
index 0000000..3a95eb6
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_blush.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_clap.png b/app/src/main/res/drawable-xhdpi/emoji_clap.png
new file mode 100644
index 0000000..d01c982
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_clap.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png b/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png
new file mode 100644
index 0000000..b9e39bc
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_confounded.png b/app/src/main/res/drawable-xhdpi/emoji_confounded.png
new file mode 100644
index 0000000..762c376
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_confounded.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_confused.png b/app/src/main/res/drawable-xhdpi/emoji_confused.png
new file mode 100644
index 0000000..8dc494d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_confused.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_cry.png b/app/src/main/res/drawable-xhdpi/emoji_cry.png
new file mode 100644
index 0000000..6d0d9af
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_cry.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_disappointed.png b/app/src/main/res/drawable-xhdpi/emoji_disappointed.png
new file mode 100644
index 0000000..8255200
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_disappointed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png b/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png
new file mode 100644
index 0000000..fa5f9e7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png b/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png
new file mode 100644
index 0000000..8001d6f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_expressionless.png b/app/src/main/res/drawable-xhdpi/emoji_expressionless.png
new file mode 100644
index 0000000..913ff4e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_expressionless.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_fearful.png b/app/src/main/res/drawable-xhdpi/emoji_fearful.png
new file mode 100644
index 0000000..513fce4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_fearful.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_flushed.png b/app/src/main/res/drawable-xhdpi/emoji_flushed.png
new file mode 100644
index 0000000..74b78c9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_flushed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_frowning.png b/app/src/main/res/drawable-xhdpi/emoji_frowning.png
new file mode 100644
index 0000000..487b770
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_frowning.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_grimacing.png b/app/src/main/res/drawable-xhdpi/emoji_grimacing.png
new file mode 100644
index 0000000..1219ba7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grimacing.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_grin.png b/app/src/main/res/drawable-xhdpi/emoji_grin.png
new file mode 100644
index 0000000..591cfce
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grin.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_grinning.png b/app/src/main/res/drawable-xhdpi/emoji_grinning.png
new file mode 100644
index 0000000..7e812b7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grinning.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png
new file mode 100644
index 0000000..0e57942
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_hushed.png b/app/src/main/res/drawable-xhdpi/emoji_hushed.png
new file mode 100644
index 0000000..bbd2cd4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_hushed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_innocent.png b/app/src/main/res/drawable-xhdpi/emoji_innocent.png
new file mode 100644
index 0000000..503b614
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_innocent.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_joy.png b/app/src/main/res/drawable-xhdpi/emoji_joy.png
new file mode 100644
index 0000000..47df693
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_joy.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing.png b/app/src/main/res/drawable-xhdpi/emoji_kissing.png
new file mode 100644
index 0000000..f3c8dcd
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png
new file mode 100644
index 0000000..449de19
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png
new file mode 100644
index 0000000..449de19
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png
new file mode 100644
index 0000000..af9a80b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png
new file mode 100644
index 0000000..57f7b49
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_laughing.png b/app/src/main/res/drawable-xhdpi/emoji_laughing.png
new file mode 100644
index 0000000..11c91eb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_laughing.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_mask.png b/app/src/main/res/drawable-xhdpi/emoji_mask.png
new file mode 100644
index 0000000..05887e9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_mask.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png b/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png
new file mode 100644
index 0000000..682a1ba
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png b/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png
new file mode 100644
index 0000000..e678020
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png b/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png
new file mode 100644
index 0000000..daf9142
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_pensive.png b/app/src/main/res/drawable-xhdpi/emoji_pensive.png
new file mode 100644
index 0000000..4159f3c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_pensive.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_persevere.png b/app/src/main/res/drawable-xhdpi/emoji_persevere.png
new file mode 100644
index 0000000..f99f6da
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_persevere.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_point_left.png b/app/src/main/res/drawable-xhdpi/emoji_point_left.png
new file mode 100644
index 0000000..38a99b4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_point_left.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_point_right.png b/app/src/main/res/drawable-xhdpi/emoji_point_right.png
new file mode 100644
index 0000000..6f9f029
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_point_right.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_rage.png b/app/src/main/res/drawable-xhdpi/emoji_rage.png
new file mode 100644
index 0000000..c65ddff
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_rage.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_relaxed.png b/app/src/main/res/drawable-xhdpi/emoji_relaxed.png
new file mode 100644
index 0000000..bbab82d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_relaxed.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_relieved.png b/app/src/main/res/drawable-xhdpi/emoji_relieved.png
new file mode 100644
index 0000000..fe5629f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_relieved.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_satisfied.png b/app/src/main/res/drawable-xhdpi/emoji_satisfied.png
new file mode 100644
index 0000000..11c91eb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_satisfied.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_scream.png b/app/src/main/res/drawable-xhdpi/emoji_scream.png
new file mode 100644
index 0000000..9e93c88
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_scream.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sleeping.png b/app/src/main/res/drawable-xhdpi/emoji_sleeping.png
new file mode 100644
index 0000000..093b852
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sleeping.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sleepy.png b/app/src/main/res/drawable-xhdpi/emoji_sleepy.png
new file mode 100644
index 0000000..df4f55e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sleepy.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_smile.png b/app/src/main/res/drawable-xhdpi/emoji_smile.png
new file mode 100644
index 0000000..81a8396
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smile.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_smiley.png b/app/src/main/res/drawable-xhdpi/emoji_smiley.png
new file mode 100644
index 0000000..77b581d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smiley.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_smirk.png b/app/src/main/res/drawable-xhdpi/emoji_smirk.png
new file mode 100644
index 0000000..bc6e508
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smirk.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sob.png b/app/src/main/res/drawable-xhdpi/emoji_sob.png
new file mode 100644
index 0000000..1561df9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sob.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png
new file mode 100644
index 0000000..fa7b58e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png
new file mode 100644
index 0000000..333716e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png
new file mode 100644
index 0000000..6ae9d49
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png b/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png
new file mode 100644
index 0000000..f2e5247
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sweat.png b/app/src/main/res/drawable-xhdpi/emoji_sweat.png
new file mode 100644
index 0000000..e894b76
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sweat.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png b/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png
new file mode 100644
index 0000000..3903f71
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png b/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png
new file mode 100644
index 0000000..41c6b82
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png b/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png
new file mode 100644
index 0000000..81786c1
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_tired_face.png b/app/src/main/res/drawable-xhdpi/emoji_tired_face.png
new file mode 100644
index 0000000..77b7834
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_tired_face.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_triumph.png b/app/src/main/res/drawable-xhdpi/emoji_triumph.png
new file mode 100644
index 0000000..92f93bd
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_triumph.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_unamused.png b/app/src/main/res/drawable-xhdpi/emoji_unamused.png
new file mode 100644
index 0000000..3722e6f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_unamused.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_weary.png b/app/src/main/res/drawable-xhdpi/emoji_weary.png
new file mode 100644
index 0000000..0c54754
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_weary.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_wink.png b/app/src/main/res/drawable-xhdpi/emoji_wink.png
new file mode 100644
index 0000000..756766d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_wink.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_worried.png b/app/src/main/res/drawable-xhdpi/emoji_worried.png
new file mode 100644
index 0000000..bfa1856
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_worried.png differ
diff --git a/app/src/main/res/drawable-xhdpi/emoji_yum.png b/app/src/main/res/drawable-xhdpi/emoji_yum.png
new file mode 100644
index 0000000..fc39637
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_yum.png differ
diff --git a/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png b/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png
new file mode 100644
index 0000000..ad27cf9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png b/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png
new file mode 100644
index 0000000..49a4ecf
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png differ
diff --git a/app/src/main/res/drawable/button_recording.xml b/app/src/main/res/drawable/button_recording.xml
new file mode 100644
index 0000000..0ae8d72
--- /dev/null
+++ b/app/src/main/res/drawable/button_recording.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/button_recordnormal.xml b/app/src/main/res/drawable/button_recordnormal.xml
new file mode 100644
index 0000000..336bcf0
--- /dev/null
+++ b/app/src/main/res/drawable/button_recordnormal.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/chat_anim_voice_left.xml b/app/src/main/res/drawable/chat_anim_voice_left.xml
new file mode 100644
index 0000000..ebb048b
--- /dev/null
+++ b/app/src/main/res/drawable/chat_anim_voice_left.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/chat_anim_voice_right.xml b/app/src/main/res/drawable/chat_anim_voice_right.xml
new file mode 100644
index 0000000..bcd953f
--- /dev/null
+++ b/app/src/main/res/drawable/chat_anim_voice_right.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/chat_btn_fail_resend.xml b/app/src/main/res/drawable/chat_btn_fail_resend.xml
new file mode 100644
index 0000000..62c4a54
--- /dev/null
+++ b/app/src/main/res/drawable/chat_btn_fail_resend.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/click_bg.xml b/app/src/main/res/drawable/click_bg.xml
new file mode 100644
index 0000000..d62509c
--- /dev/null
+++ b/app/src/main/res/drawable/click_bg.xml
@@ -0,0 +1,17 @@
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/frame_bg.xml b/app/src/main/res/drawable/frame_bg.xml
new file mode 100644
index 0000000..c954432
--- /dev/null
+++ b/app/src/main/res/drawable/frame_bg.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/frame_blue_to_thin_bg.xml b/app/src/main/res/drawable/frame_blue_to_thin_bg.xml
new file mode 100644
index 0000000..210092a
--- /dev/null
+++ b/app/src/main/res/drawable/frame_blue_to_thin_bg.xml
@@ -0,0 +1,23 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/item_divider_line.xml b/app/src/main/res/drawable/item_divider_line.xml
new file mode 100644
index 0000000..8cf968c
--- /dev/null
+++ b/app/src/main/res/drawable/item_divider_line.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/msg_input_shape.xml b/app/src/main/res/drawable/msg_input_shape.xml
new file mode 100644
index 0000000..81a19ec
--- /dev/null
+++ b/app/src/main/res/drawable/msg_input_shape.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/white_radius.xml b/app/src/main/res/drawable/white_radius.xml
new file mode 100644
index 0000000..72390ab
--- /dev/null
+++ b/app/src/main/res/drawable/white_radius.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/xmxq_tc_bj.9.png b/app/src/main/res/drawable/xmxq_tc_bj.9.png
new file mode 100644
index 0000000..53f545c
Binary files /dev/null and b/app/src/main/res/drawable/xmxq_tc_bj.9.png differ
diff --git a/app/src/main/res/layout/activity_service_chat.xml b/app/src/main/res/layout/activity_service_chat.xml
new file mode 100644
index 0000000..268fe14
--- /dev/null
+++ b/app/src/main/res/layout/activity_service_chat.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/chat_emotion_gridview.xml b/app/src/main/res/layout/chat_emotion_gridview.xml
new file mode 100644
index 0000000..963734e
--- /dev/null
+++ b/app/src/main/res/layout/chat_emotion_gridview.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_emotion_item.xml b/app/src/main/res/layout/chat_emotion_item.xml
new file mode 100644
index 0000000..9e5a592
--- /dev/null
+++ b/app/src/main/res/layout/chat_emotion_item.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_image_brower_layout.xml b/app/src/main/res/layout/chat_image_brower_layout.xml
new file mode 100644
index 0000000..c45b144
--- /dev/null
+++ b/app/src/main/res/layout/chat_image_brower_layout.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_input_bottom_bar_layout.xml b/app/src/main/res/layout/chat_input_bottom_bar_layout.xml
new file mode 100644
index 0000000..a2280cf
--- /dev/null
+++ b/app/src/main/res/layout/chat_input_bottom_bar_layout.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_image_layout.xml b/app/src/main/res/layout/chat_item_image_layout.xml
new file mode 100644
index 0000000..19d77fc
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_image_layout.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_left_audio_layout.xml b/app/src/main/res/layout/chat_item_left_audio_layout.xml
new file mode 100644
index 0000000..d5cad55
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_left_audio_layout.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_left_layout.xml b/app/src/main/res/layout/chat_item_left_layout.xml
new file mode 100644
index 0000000..847d2ef
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_left_layout.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_left_text_layout.xml b/app/src/main/res/layout/chat_item_left_text_layout.xml
new file mode 100644
index 0000000..d3b600b
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_left_text_layout.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_right_audio_layout.xml b/app/src/main/res/layout/chat_item_right_audio_layout.xml
new file mode 100644
index 0000000..afb0108
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_right_audio_layout.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_right_image_layout.xml b/app/src/main/res/layout/chat_item_right_image_layout.xml
new file mode 100644
index 0000000..f255cc8
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_right_image_layout.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_right_layout.xml b/app/src/main/res/layout/chat_item_right_layout.xml
new file mode 100644
index 0000000..8f50cc0
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_right_layout.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_item_right_text_layout.xml b/app/src/main/res/layout/chat_item_right_text_layout.xml
new file mode 100644
index 0000000..6cc141d
--- /dev/null
+++ b/app/src/main/res/layout/chat_item_right_text_layout.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_mess_iv_listitem.xml b/app/src/main/res/layout/layout_mess_iv_listitem.xml
new file mode 100644
index 0000000..65f9bfe
--- /dev/null
+++ b/app/src/main/res/layout/layout_mess_iv_listitem.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_tongbaobottom.xml b/app/src/main/res/layout/layout_tongbaobottom.xml
new file mode 100644
index 0000000..1cc81d9
--- /dev/null
+++ b/app/src/main/res/layout/layout_tongbaobottom.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_view_headicon.xml b/app/src/main/res/layout/layout_view_headicon.xml
new file mode 100644
index 0000000..b9dd628
--- /dev/null
+++ b/app/src/main/res/layout/layout_view_headicon.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_voice_dialog_manager.xml b/app/src/main/res/layout/layout_voice_dialog_manager.xml
new file mode 100644
index 0000000..3331d1a
--- /dev/null
+++ b/app/src/main/res/layout/layout_voice_dialog_manager.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png
new file mode 100644
index 0000000..5669edf
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png
new file mode 100644
index 0000000..d164682
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_more.png b/app/src/main/res/mipmap-hdpi/ic_btn_more.png
new file mode 100644
index 0000000..dda5ff1
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_more.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_voice.png b/app/src/main/res/mipmap-hdpi/ic_btn_voice.png
new file mode 100644
index 0000000..f731870
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_voice.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-hdpi/tb_bottom_images.png b/app/src/main/res/mipmap-hdpi/tb_bottom_images.png
new file mode 100644
index 0000000..a0e1129
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/tb_bottom_images.png differ
diff --git a/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png
new file mode 100644
index 0000000..2acebac
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/cancel.png b/app/src/main/res/mipmap-xhdpi/cancel.png
new file mode 100644
index 0000000..519218c
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/cancel.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/emoji_icon.png b/app/src/main/res/mipmap-xhdpi/emoji_icon.png
new file mode 100644
index 0000000..a9b1a18
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/emoji_icon.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png
new file mode 100644
index 0000000..4c2cfb6
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png
new file mode 100644
index 0000000..7214656
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_more.png b/app/src/main/res/mipmap-xhdpi/ic_btn_more.png
new file mode 100644
index 0000000..3179b31
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_more.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png b/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png
new file mode 100644
index 0000000..28b92a5
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg b/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg
new file mode 100644
index 0000000..9d50a96
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg b/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg
new file mode 100644
index 0000000..55dad3a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png b/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png
new file mode 100644
index 0000000..a6b2b50
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png b/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png
new file mode 100644
index 0000000..f38a0ce
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png
new file mode 100644
index 0000000..91ef677
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png
new file mode 100644
index 0000000..2b1d7a6
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png
new file mode 100644
index 0000000..f85f25b
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png b/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png
new file mode 100644
index 0000000..d200d97
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice1.png b/app/src/main/res/mipmap-xhdpi/tb_voice1.png
new file mode 100644
index 0000000..daee17f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice1.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice2.png b/app/src/main/res/mipmap-xhdpi/tb_voice2.png
new file mode 100644
index 0000000..81f930f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice2.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice3.png b/app/src/main/res/mipmap-xhdpi/tb_voice3.png
new file mode 100644
index 0000000..b801872
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice3.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/voice_to_short.png b/app/src/main/res/mipmap-xhdpi/voice_to_short.png
new file mode 100644
index 0000000..16bc857
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/voice_to_short.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png
new file mode 100644
index 0000000..03366e4
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png
new file mode 100644
index 0000000..cfea9a5
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png
new file mode 100644
index 0000000..d90017e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png
new file mode 100644
index 0000000..754d201
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png b/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png
new file mode 100644
index 0000000..db1cea8
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png
new file mode 100644
index 0000000..8860bc3
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..ad44270
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..808981f
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,18 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
+ #503097e6
+ #3097e6
+ #ffffff
+ #f0f2f6
+ #7f000000
+ #d9d9d9
+ #051b28
+ #e6e6e6
+ #909090
+ #e83060
+ #88e83060
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..07bad09
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+ ChatDemo
+
+
+ 按住 说话
+ 松开 结束
+ 松开手指,取消发送
+ 手指上滑,取消发送
+ 录音时间过短
+ 录音时间过长
+
+ 相册
+ 拍照
+ 表情
+ 发送
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3ec8f06
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java b/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java
new file mode 100644
index 0000000..187f466
--- /dev/null
+++ b/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.administrator.chatdemo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..221c810
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ classpath 'com.novoda:bintray-release:0.3.4'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..63f9152
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Jul 18 16:32:52 CST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'