Full Code of stridercheng/chatui for AI

master 5d9ac4d1d16f cached
94 files
293.5 KB
71.7k tokens
418 symbols
1 requests
Download .txt
Showing preview only (328K chars total). Download the full file or copy to clipboard to get everything.
Repository: stridercheng/chatui
Branch: master
Commit: 5d9ac4d1d16f
Files: 94
Total size: 293.5 KB

Directory structure:
gitextract_em0rdfsy/

├── .gitignore
├── .idea/
│   ├── .name
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── encodings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── libs/
│   │   └── BaiduLBS_Android.jar
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── rance/
│       │               └── chatui/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── rance/
│       │   │           └── chatui/
│       │   │               ├── adapter/
│       │   │               │   ├── ChatAdapter.java
│       │   │               │   ├── CommonFragmentPagerAdapter.java
│       │   │               │   ├── ContactAdapter.java
│       │   │               │   ├── EmotionGridViewAdapter.java
│       │   │               │   ├── EmotionPagerAdapter.java
│       │   │               │   └── holder/
│       │   │               │       ├── BaseViewHolder.java
│       │   │               │       ├── ChatAcceptViewHolder.java
│       │   │               │       └── ChatSendViewHolder.java
│       │   │               ├── base/
│       │   │               │   ├── BaseFragment.java
│       │   │               │   └── MyApplication.java
│       │   │               ├── enity/
│       │   │               │   ├── FullImageInfo.java
│       │   │               │   ├── IMContact.java
│       │   │               │   ├── Link.java
│       │   │               │   └── MessageInfo.java
│       │   │               ├── ui/
│       │   │               │   ├── activity/
│       │   │               │   │   ├── ContactActivity.java
│       │   │               │   │   ├── FullImageActivity.java
│       │   │               │   │   └── IMActivity.java
│       │   │               │   └── fragment/
│       │   │               │       ├── ChatEmotionFragment.java
│       │   │               │       └── ChatFunctionFragment.java
│       │   │               ├── util/
│       │   │               │   ├── AudioRecorderUtils.java
│       │   │               │   ├── CheckPermissionUtils.java
│       │   │               │   ├── Constants.java
│       │   │               │   ├── EmotionUtils.java
│       │   │               │   ├── FileUtils.java
│       │   │               │   ├── GifOpenHelper.java
│       │   │               │   ├── GlobalOnItemClickManagerUtils.java
│       │   │               │   ├── MediaManager.java
│       │   │               │   ├── MessageCenter.java
│       │   │               │   ├── PhotoUtils.java
│       │   │               │   ├── PopupWindowFactory.java
│       │   │               │   └── Utils.java
│       │   │               └── widget/
│       │   │                   ├── BubbleDrawable.java
│       │   │                   ├── BubbleImageView.java
│       │   │                   ├── BubbleLinearLayout.java
│       │   │                   ├── ChatContextMenu.java
│       │   │                   ├── EmotionInputDetector.java
│       │   │                   ├── GifTextView.java
│       │   │                   ├── IndicatorView.java
│       │   │                   ├── NoScrollViewPager.java
│       │   │                   └── StateButton.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── bg_circle_gary.xml
│       │       │   ├── bg_circle_white.xml
│       │       │   ├── bg_surname.xml
│       │       │   ├── corners_edit.xml
│       │       │   ├── corners_edit_white.xml
│       │       │   ├── divider.xml
│       │       │   ├── record_microphone.xml
│       │       │   ├── record_microphone_bj.xml
│       │       │   ├── voice_left.xml
│       │       │   └── voice_right.xml
│       │       ├── layout/
│       │       │   ├── activity_contact.xml
│       │       │   ├── activity_full_image.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── dialog_contact.xml
│       │       │   ├── fragment_chat_emotion.xml
│       │       │   ├── fragment_chat_function.xml
│       │       │   ├── include_reply_layout.xml
│       │       │   ├── item_chat_accept.xml
│       │       │   ├── item_chat_send.xml
│       │       │   ├── item_contact.xml
│       │       │   ├── layout_microphone.xml
│       │       │   └── popup_context_menu.xml
│       │       ├── values/
│       │       │   ├── attr.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       ├── values-w820dp/
│       │       │   └── dimens.xml
│       │       └── xml/
│       │           └── file_paths.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── rance/
│                       └── chatui/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries

# Keystore files
*.jks

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# Google Services (e.g. APIs or Firebase)
google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json


================================================
FILE: .idea/.name
================================================
ChatUI

================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <resourceExtensions />
    <wildcardResourcePatterns>
      <entry name="!?*.java" />
      <entry name="!?*.form" />
      <entry name="!?*.class" />
      <entry name="!?*.groovy" />
      <entry name="!?*.scala" />
      <entry name="!?*.flex" />
      <entry name="!?*.kt" />
      <entry name="!?*.clj" />
      <entry name="!?*.aj" />
    </wildcardResourcePatterns>
    <annotationProcessing>
      <profile default="true" name="Default" enabled="false">
        <processorPath useClasspath="true" />
      </profile>
    </annotationProcessing>
  </component>
</project>

================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="" />
</component>

================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="EntryPointsManager">
    <entry_points version="2.0" />
  </component>
  <component name="NullableNotNullManager">
    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
    <option name="myNullables">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
        </list>
      </value>
    </option>
    <option name="myNotNulls">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
        </list>
      </value>
    </option>
  </component>
  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
    <OptionsSetting value="true" id="Add" />
    <OptionsSetting value="true" id="Remove" />
    <OptionsSetting value="true" id="Checkout" />
    <OptionsSetting value="true" id="Update" />
    <OptionsSetting value="true" id="Status" />
    <OptionsSetting value="true" id="Edit" />
    <ConfirmationsSetting value="0" id="Add" />
    <ConfirmationsSetting value="0" id="Remove" />
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/ChatUI.iml" filepath="$PROJECT_DIR$/ChatUI.iml" />
      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
修改自开源项目:https://github.com/Rance935/ChatUI 

## 修改内容
+ 替换部分图片资源
+ 用源生RecyclerView替换了EasyRecyclerView,比较相信官方。
+ 修复拍照在7.0系统中无法获取权限问题。
+ 增加文件分享,点击文件打开。
+ 增加联系人信息分享。
+ 长按消息弹出上下文菜单
+ 注册app到系统分享面板,能够处理分享自外部的URL、图片、文档。

## 截图
![image](https://github.com/stridercheng/chatui/raw/master/images/preview.png)

## 问题
代码结构待优化


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"

    aaptOptions {
        cruncherEnabled = false
    }

    defaultConfig {
        applicationId "com.rance.chatui"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //noinspection GradleCompatible
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support:design:24.2.1'
    compile 'com.android.support:support-v4:24.2.1'
    compile 'org.greenrobot:eventbus:3.0.0'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.labo.kaji:relativepopupwindow:0.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\Administrator\AppData\Local\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 *;
#}


================================================
FILE: app/src/androidTest/java/com/rance/chatui/ApplicationTest.java
================================================
package com.rance.chatui;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.rance.chatui">

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

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application
        android:name=".base.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".ui.activity.IMActivity"
            android:configChanges="orientation|keyboardHidden|navigation|screenSize"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateHidden|adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="android.intent.action.SEND" />
                <data android:mimeType="*/*" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.activity.FullImageActivity"
            android:configChanges="orientation|keyboardHidden|navigation|screenSize"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Translucent"
            android:windowSoftInputMode="stateHidden|adjustPan" />

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.chatui.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

        <activity android:name=".ui.activity.ContactActivity"
            android:screenOrientation="portrait"/>
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/rance/chatui/adapter/ChatAdapter.java
================================================
package com.rance.chatui.adapter;

import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.rance.chatui.adapter.holder.BaseViewHolder;
import com.rance.chatui.adapter.holder.ChatAcceptViewHolder;
import com.rance.chatui.adapter.holder.ChatSendViewHolder;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.util.Constants;

import java.util.ArrayList;
import java.util.List;

/**
 * 作者:Rance on 2016/11/29 10:46
 * 邮箱:rance935@163.com
 */
public class ChatAdapter extends RecyclerView.Adapter<BaseViewHolder> {

    private onItemClickListener onItemClickListener;
    public Handler handler;
    private List<MessageInfo> messageInfoList;

    public ChatAdapter(List<MessageInfo> messageInfoList) {
        handler = new Handler();
        this.messageInfoList = messageInfoList;
    }

//    @Override
//    public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
//        BaseViewHolder viewHolder = null;
//        switch (viewType) {
//            case Constants.CHAT_ITEM_TYPE_LEFT:
//                viewHolder = new ChatAcceptViewHolder(parent, onItemClickListener, handler);
//                break;
//            case Constants.CHAT_ITEM_TYPE_RIGHT:
//                viewHolder = new ChatSendViewHolder(parent, onItemClickListener, handler);
//                break;
//        }
//        return viewHolder;
//    }
//
//    @Override
//    public int getViewType(int position) {
//        return getAllData().get(position).getType();
//    }

    public void addItemClickListener(onItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        BaseViewHolder viewHolder = null;
        switch (viewType) {
            case Constants.CHAT_ITEM_TYPE_LEFT:
                viewHolder = new ChatAcceptViewHolder(parent, onItemClickListener, handler);
                break;
            case Constants.CHAT_ITEM_TYPE_RIGHT:
                viewHolder = new ChatSendViewHolder(parent, onItemClickListener, handler);
                break;
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        holder.itemView.setTag(position);
        holder.setData(messageInfoList.get(position));
    }

    @Override
    public int getItemViewType(int position) {
        return messageInfoList.get(position).getType();
    }

    @Override
    public int getItemCount() {
        if (messageInfoList == null) {
            return 0;
        } else {
            return messageInfoList.size();
        }
    }

    public void addAll(List<MessageInfo> messageInfos) {
        if (messageInfoList == null) {
            messageInfoList = messageInfos;
        } else {
            messageInfoList.addAll(messageInfos);
        }

        notifyDataSetChanged();
    }

    public void add(MessageInfo messageInfo) {
        if (messageInfoList == null) {
            messageInfoList = new ArrayList<>();
        }

        messageInfoList.add(messageInfo);

        notifyDataSetChanged();
    }

    public interface onItemClickListener {
        void onHeaderClick(int position);

        void onImageClick(View view, int position);

        void onVoiceClick(ImageView imageView, int position);

        void onFileClick(View view, int position);

        void onLinkClick(View view, int position);

        void onLongClickImage(View view, int position);

        void onLongClickText(View view, int position);

        void onLongClickItem(View view, int position);

        void onLongClickFile(View view, int position);

        void onLongClickLink(View view, int position);
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/CommonFragmentPagerAdapter.java
================================================
package com.rance.chatui.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import java.util.ArrayList;

/**
 * 作者:Rance on 2016/11/25 16:36
 * 邮箱:rance935@163.com
 */
public class CommonFragmentPagerAdapter extends FragmentPagerAdapter {
    ArrayList<Fragment> list;

    public CommonFragmentPagerAdapter(FragmentManager fm, ArrayList<Fragment> list) {
        super(fm);
        this.list = list;
    }

    @Override
    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    public int getCount() {
        return list != null ? list.size() : 0;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/ContactAdapter.java
================================================
package com.rance.chatui.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.rance.chatui.R;
import com.rance.chatui.enity.IMContact;

import java.util.List;


/**
 * Created by chengz
 *
 * @date 2017/8/3.
 */

public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder>
        implements View.OnClickListener {
    private List<IMContact> imContactList;
    private OnContactClickListener mOnContactClickListener;

    public ContactAdapter(List<IMContact> imContactList) {
        this.imContactList = imContactList;
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
        ViewHolder viewHolder = new ViewHolder(view);
        viewHolder.itemView.setOnClickListener(this);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        IMContact imContact = imContactList.get(position);
        holder.tvName.setText(imContact.getName());
        holder.tvPhone.setText(imContact.getPhonenumber());

        holder.itemView.setTag(imContact);
    }

    @Override
    public int getItemCount() {
        return imContactList.size();
    }

    @Override
    public void onClick(View v) {
        if (mOnContactClickListener == null) {
            return;
        }

        mOnContactClickListener.onContactClick(v, (IMContact) v.getTag());
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;
        TextView tvPhone;
        public ViewHolder(View itemView) {
            super(itemView);
            tvName = (TextView) itemView.findViewById(R.id.tv_name);
            tvPhone = (TextView) itemView.findViewById(R.id.tv_phone);
        }
    }

    public interface OnContactClickListener {
        void onContactClick(View view, IMContact imContact);
    }

    public void setOnContactClickListener(OnContactClickListener onContactClickListener) {
        this.mOnContactClickListener = onContactClickListener;
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/EmotionGridViewAdapter.java
================================================
package com.rance.chatui.adapter;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.ImageView;

import com.rance.chatui.R;
import com.rance.chatui.util.EmotionUtils;

import java.util.List;
/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class EmotionGridViewAdapter extends BaseAdapter {

    private Context context;
    private List<String> emotionNames;
    private int itemWidth;

    public EmotionGridViewAdapter(Context context, List<String> emotionNames, int itemWidth) {
        this.context = context;
        this.emotionNames = emotionNames;
        this.itemWidth = itemWidth;
    }

    @Override
    public int getCount() {
        // +1 最后一个为删除按钮
        return emotionNames.size() + 1;
    }

    @Override
    public String getItem(int position) {
        return emotionNames.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView iv_emotion = new ImageView(context);
        // 设置内边距
        iv_emotion.setPadding(itemWidth / 8, itemWidth / 8, itemWidth / 8, itemWidth / 8);
        LayoutParams params = new LayoutParams(itemWidth, itemWidth);
        iv_emotion.setLayoutParams(params);

        //判断是否为最后一个item
        if (position == getCount() - 1) {
            iv_emotion.setImageResource(R.drawable.compose_emotion_delete);
        } else {
            String emotionName = emotionNames.get(position);
            iv_emotion.setImageResource(EmotionUtils.EMOTION_STATIC_MAP.get(emotionName));
        }

        return iv_emotion;
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/EmotionPagerAdapter.java
================================================
package com.rance.chatui.adapter;

import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;

import java.util.List;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class EmotionPagerAdapter extends PagerAdapter {

	private List<GridView> gvs;

	public EmotionPagerAdapter(List<GridView> gvs) {
		this.gvs = gvs;
	}

	@Override
	public int getCount() {
		return gvs.size();
	}

	@Override
	public boolean isViewFromObject(View arg0, Object arg1) {
		return arg0 == arg1;
	}

	@Override
	public void destroyItem(ViewGroup container, int position, Object object) {
		((ViewPager) container).removeView(gvs.get(position));
	}

	@Override
	public Object instantiateItem(ViewGroup container, int position) {
		((ViewPager) container).addView(gvs.get(position));
		return gvs.get(position);
	}

}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/holder/BaseViewHolder.java
================================================
package com.rance.chatui.adapter.holder;

import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created by chengz
 *
 * @date 2017/8/3.
 */

public class BaseViewHolder<M> extends RecyclerView.ViewHolder {

    public BaseViewHolder(View itemView) {
        super(itemView);
    }

    public void setData(M data) {

    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/holder/ChatAcceptViewHolder.java
================================================
package com.rance.chatui.adapter.holder;

import android.content.Context;
import android.os.Handler;
import android.text.TextPaint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.rance.chatui.R;
import com.rance.chatui.adapter.ChatAdapter;
import com.rance.chatui.enity.IMContact;
import com.rance.chatui.enity.Link;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.util.Constants;
import com.rance.chatui.util.FileUtils;
import com.rance.chatui.util.Utils;
import com.rance.chatui.widget.BubbleImageView;
import com.rance.chatui.widget.BubbleLinearLayout;
import com.rance.chatui.widget.GifTextView;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class ChatAcceptViewHolder extends BaseViewHolder<MessageInfo> {
    private static final String TAG = "ChatAcceptViewHolder";
    TextView chatItemDate;
    ImageView chatItemHeader;
    GifTextView chatItemContentText;
    BubbleImageView chatItemContentImage;
    ImageView chatItemVoice;
    BubbleLinearLayout chatItemLayoutContent;
    TextView chatItemVoiceTime;
    BubbleLinearLayout chatItemLayoutFile;
    ImageView ivFileType;
    TextView tvFileName;
    TextView tvFileSize;

    BubbleLinearLayout chatItemLayoutContact;
    TextView tvContactSurname;
    TextView tvContactName;
    TextView tvContactPhone;

    BubbleLinearLayout chatItemLayoutLink;
    TextView tvLinkSubject;
    TextView tvLinkText;
    ImageView ivLinkPicture;
    private ChatAdapter.onItemClickListener onItemClickListener;
    private Handler handler;
    private RelativeLayout.LayoutParams layoutParams;
    private Context mContext;

    public ChatAcceptViewHolder(ViewGroup parent, ChatAdapter.onItemClickListener onItemClickListener, Handler handler) {
        super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_accept, parent, false));
        findViewByIds(itemView);
        setItemLongClick();
        setItemClick();
        this.mContext = parent.getContext();
        this.onItemClickListener = onItemClickListener;
        this.handler = handler;
        layoutParams = (RelativeLayout.LayoutParams) chatItemLayoutContent.getLayoutParams();
    }

    private void findViewByIds(View itemView) {
        chatItemDate = (TextView) itemView.findViewById(R.id.chat_item_date);
        chatItemHeader = (ImageView) itemView.findViewById(R.id.chat_item_header);
        chatItemContentText = (GifTextView) itemView.findViewById(R.id.chat_item_content_text);
        chatItemContentImage = (BubbleImageView) itemView.findViewById(R.id.chat_item_content_image);
        chatItemVoice = (ImageView) itemView.findViewById(R.id.chat_item_voice);
        chatItemLayoutContent = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_content);
        chatItemVoiceTime = (TextView) itemView.findViewById(R.id.chat_item_voice_time);
        chatItemLayoutFile = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_file);
        ivFileType = (ImageView) itemView.findViewById(R.id.iv_file_type);
        tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name);
        tvFileSize = (TextView) itemView.findViewById(R.id.tv_file_size);
        chatItemLayoutContact = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_contact);
        tvContactSurname = (TextView) itemView.findViewById(R.id.tv_contact_surname);
        tvContactPhone = (TextView) itemView.findViewById(R.id.tv_contact_phone);
        chatItemLayoutLink = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_link);
        tvLinkSubject = (TextView) itemView.findViewById(R.id.tv_link_subject);
        tvLinkText = (TextView) itemView.findViewById(R.id.tv_link_text);
        ivLinkPicture = (ImageView) itemView.findViewById(R.id.iv_link_picture);
    }

    @Override
    public void setData(MessageInfo data) {
        chatItemDate.setText(data.getTime() != null ? data.getTime() : "");
        Glide.with(mContext).load(data.getHeader()).into(chatItemHeader);
        chatItemHeader.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onHeaderClick((Integer) itemView.getTag());
            }
        });
        switch (data.getFileType()) {
            case Constants.CHAT_FILE_TYPE_TEXT:
                chatItemContentText.setSpanText(handler, data.getContent(), true);
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.VISIBLE);
                chatItemLayoutContent.setVisibility(View.VISIBLE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);

                TextPaint paint = chatItemContentText.getPaint();
                // 计算textview在屏幕上占多宽
                int len = (int) paint.measureText(chatItemContentText.getText().toString().trim());
                if (len < Utils.dp2px(mContext, 200)){
                    layoutParams.width = len + Utils.dp2px(mContext, 30);
                } else {
                    layoutParams.width = LinearLayout.LayoutParams.MATCH_PARENT;
                }
                layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_IMAGE:
                chatItemVoice.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.VISIBLE);
                chatItemLayoutContact.setVisibility(View.GONE);

                Glide.with(mContext).load(data.getFilepath()).into(chatItemContentImage);
                chatItemContentImage.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());
                    }
                });
                layoutParams.width = Utils.dp2px(mContext, 120);
                layoutParams.height = Utils.dp2px(mContext, 48);
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_VOICE:
                chatItemVoice.setVisibility(View.VISIBLE);
                chatItemLayoutContent.setVisibility(View.VISIBLE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.VISIBLE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);

                chatItemVoiceTime.setText(Utils.formatTime(data.getVoiceTime()));
                chatItemLayoutContent.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());
                    }
                });
                layoutParams.width = Utils.dp2px(mContext, 120);
                layoutParams.height = Utils.dp2px(mContext, 48);
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_FILE:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);

//                chatItemLayoutContent.setVisibility(View.VISIBLE);
                chatItemLayoutFile.setVisibility(View.VISIBLE);
                tvFileName.setText(FileUtils.getFileName(data.getFilepath()));
                try {
                    tvFileSize.setText(FileUtils.getFileSize(data.getFilepath()));
                    switch (FileUtils.getExtensionName(data.getFilepath())) {
                        case "doc":
                        case "docx":
                            ivFileType.setImageResource(R.mipmap.icon_file_word);
                            break;
                        case "ppt":
                        case "pptx":
                            ivFileType.setImageResource(R.mipmap.icon_file_ppt);
                            break;
                        case "xls":
                        case "xlsx":
                            ivFileType.setImageResource(R.mipmap.icon_file_excel);
                            break;
                        case "pdf":
                            ivFileType.setImageResource(R.mipmap.icon_file_pdf);
                            break;
                        default:
                            ivFileType.setImageResource(R.mipmap.icon_file_other);
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case Constants.CHAT_FILE_TYPE_CONTACT:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);

                chatItemLayoutContact.setVisibility(View.VISIBLE);

                IMContact imContact = (IMContact) data.getObject();
                tvContactSurname.setText(imContact.getSurname());
                tvContactName.setText(imContact.getName());
                tvContactPhone.setText(imContact.getPhonenumber());
                break;
            case Constants.CHAT_FILE_TYPE_LINK:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);

                chatItemLayoutLink.setVisibility(View.VISIBLE);
                Link link = (Link) data.getObject();

                tvLinkSubject.setText(link.getSubject());
                tvLinkText.setText(link.getText());
                Glide.with(mContext).load(link.getStream()).into(ivLinkPicture);
                break;
        }
    }

    public void setItemLongClick() {
        chatItemContentImage.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickImage(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemContentText.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickText(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemLayoutContent.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickItem(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemLayoutFile.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickFile(v, (Integer) itemView.getTag());
                return true;
            }
        });
    }

    public void setItemClick() {
        chatItemContentImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onFileClick(v, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutLink.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onLinkClick(v, (Integer) itemView.getTag());
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/adapter/holder/ChatSendViewHolder.java
================================================
package com.rance.chatui.adapter.holder;

import android.content.Context;
import android.os.Handler;
import android.support.v4.content.ContextCompat;
import android.text.TextPaint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.rance.chatui.R;
import com.rance.chatui.adapter.ChatAdapter;
import com.rance.chatui.enity.IMContact;
import com.rance.chatui.enity.Link;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.util.Constants;
import com.rance.chatui.util.FileUtils;
import com.rance.chatui.util.Utils;
import com.rance.chatui.widget.BubbleImageView;
import com.rance.chatui.widget.BubbleLinearLayout;
import com.rance.chatui.widget.GifTextView;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class ChatSendViewHolder extends BaseViewHolder<MessageInfo> {
    private static final String TAG = "ChatSendViewHolder";
    TextView chatItemDate;
    ImageView chatItemHeader;
    GifTextView chatItemContentText;
    BubbleImageView chatItemContentImage;
    ImageView chatItemFail;
    ProgressBar chatItemProgress;
    ImageView chatItemVoice;
    BubbleLinearLayout chatItemLayoutContent;
    TextView chatItemVoiceTime;
    BubbleLinearLayout chatItemLayoutFile;
    ImageView ivFileType;
    TextView tvFileName;
    TextView tvFileSize;

    BubbleLinearLayout chatItemLayoutContact;
    TextView tvContactSurname;
    TextView tvContactName;
    TextView tvContactPhone;

    BubbleLinearLayout chatItemLayoutLink;
    TextView tvLinkSubject;
    TextView tvLinkText;
    ImageView ivLinkPicture;
    private ChatAdapter.onItemClickListener onItemClickListener;
    private Handler handler;
    private RelativeLayout.LayoutParams layoutParams;
    private Context mContext;

    public ChatSendViewHolder(ViewGroup parent, ChatAdapter.onItemClickListener onItemClickListener, Handler handler) {
        super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_send, parent, false));
        findViewByIds(itemView);
        setItemLongClick();
        setItemClick();
        this.mContext = parent.getContext();
        this.onItemClickListener = onItemClickListener;
        this.handler = handler;
        layoutParams = (RelativeLayout.LayoutParams) chatItemLayoutContent.getLayoutParams();
    }

    private void findViewByIds(View itemView) {
        chatItemDate = (TextView) itemView.findViewById(R.id.chat_item_date);
        chatItemHeader = (ImageView) itemView.findViewById(R.id.chat_item_header);
        chatItemContentText = (GifTextView) itemView.findViewById(R.id.chat_item_content_text);
        chatItemFail = (ImageView) itemView.findViewById(R.id.chat_item_fail);
        chatItemProgress = (ProgressBar) itemView.findViewById(R.id.chat_item_progress);
        chatItemContentImage = (BubbleImageView) itemView.findViewById(R.id.chat_item_content_image);
        chatItemVoice = (ImageView) itemView.findViewById(R.id.chat_item_voice);
        chatItemLayoutContent = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_content);
        chatItemVoiceTime = (TextView) itemView.findViewById(R.id.chat_item_voice_time);
        chatItemLayoutFile = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_file);
        ivFileType = (ImageView) itemView.findViewById(R.id.iv_file_type);
        tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name);
        tvFileSize = (TextView) itemView.findViewById(R.id.tv_file_size);
        chatItemLayoutContact = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_contact);
        tvContactSurname = (TextView) itemView.findViewById(R.id.tv_contact_surname);
        tvContactPhone = (TextView) itemView.findViewById(R.id.tv_contact_phone);
        chatItemLayoutLink = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_link);
        tvLinkSubject = (TextView) itemView.findViewById(R.id.tv_link_subject);
        tvLinkText = (TextView) itemView.findViewById(R.id.tv_link_text);
        ivLinkPicture = (ImageView) itemView.findViewById(R.id.iv_link_picture);
    }


    @Override
    public void setData(MessageInfo data) {
        chatItemDate.setText(data.getTime() != null ? data.getTime() : "");
        Glide.with(mContext).load(data.getHeader()).into(chatItemHeader);
        chatItemHeader.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onHeaderClick((Integer) itemView.getTag());
            }
        });
        switch (data.getFileType()) {
            case Constants.CHAT_FILE_TYPE_TEXT:
                chatItemContentText.setSpanText(handler, data.getContent(), true);
                chatItemVoice.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);
                chatItemLayoutLink.setVisibility(View.GONE);

                chatItemContentText.setVisibility(View.VISIBLE);
                chatItemLayoutContent.setVisibility(View.VISIBLE);
                TextPaint paint = chatItemContentText.getPaint();
                paint.setColor(ContextCompat.getColor(mContext, R.color.chat_send_text));
                // 计算textview在屏幕上占多宽
                int len = (int) paint.measureText(chatItemContentText.getText().toString().trim());
                if (len < Utils.dp2px(mContext, 200)){
                    layoutParams.width = len + Utils.dp2px(mContext, 30);
                } else {
                    layoutParams.width = LinearLayout.LayoutParams.MATCH_PARENT;
                }
                layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_IMAGE:
                chatItemVoice.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);
                chatItemLayoutLink.setVisibility(View.GONE);

                chatItemContentImage.setVisibility(View.VISIBLE);
                Glide.with(mContext).load(data.getFilepath()).into(chatItemContentImage);
                layoutParams.width = Utils.dp2px(mContext, 120);
                layoutParams.height = Utils.dp2px(mContext, 48);
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_VOICE:
                chatItemVoice.setVisibility(View.VISIBLE);
                chatItemLayoutContent.setVisibility(View.VISIBLE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.VISIBLE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);
                chatItemLayoutLink.setVisibility(View.GONE);

                chatItemLayoutFile.setVisibility(View.GONE);
                chatItemVoiceTime.setText(Utils.formatTime(data.getVoiceTime()));
                layoutParams.width = Utils.dp2px(mContext, 120);
                layoutParams.height = Utils.dp2px(mContext, 48);
                chatItemLayoutContent.setLayoutParams(layoutParams);
                break;
            case Constants.CHAT_FILE_TYPE_FILE:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);
                chatItemLayoutLink.setVisibility(View.GONE);

                chatItemLayoutFile.setVisibility(View.VISIBLE);
                tvFileName.setText(FileUtils.getFileName(data.getFilepath()));
                try {
                    tvFileSize.setText(FileUtils.getFileSize(data.getFilepath()));
                    switch (FileUtils.getExtensionName(data.getFilepath())) {
                        case "doc":
                        case "docx":
                            ivFileType.setImageResource(R.mipmap.icon_file_word);
                            break;
                        case "ppt":
                        case "pptx":
                            ivFileType.setImageResource(R.mipmap.icon_file_ppt);
                            break;
                        case "xls":
                        case "xlsx":
                            ivFileType.setImageResource(R.mipmap.icon_file_excel);
                            break;
                        case "pdf":
                            ivFileType.setImageResource(R.mipmap.icon_file_pdf);
                            break;
                        default:
                            ivFileType.setImageResource(R.mipmap.icon_file_other);
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case Constants.CHAT_FILE_TYPE_CONTACT:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);

                chatItemLayoutContact.setVisibility(View.VISIBLE);

                IMContact imContact = (IMContact) data.getObject();
                tvContactSurname.setText(imContact.getSurname());
                tvContactName.setText(imContact.getName());
                tvContactPhone.setText(imContact.getPhonenumber());
                break;
            case Constants.CHAT_FILE_TYPE_LINK:
                chatItemVoice.setVisibility(View.GONE);
                chatItemContentText.setVisibility(View.GONE);
                chatItemContentImage.setVisibility(View.GONE);
                chatItemVoiceTime.setVisibility(View.GONE);
                chatItemLayoutContent.setVisibility(View.GONE);
                chatItemLayoutFile.setVisibility(View.GONE);
                chatItemLayoutContact.setVisibility(View.GONE);

                chatItemLayoutLink.setVisibility(View.VISIBLE);
                Link link = (Link) data.getObject();

                tvLinkSubject.setText(link.getSubject());
                tvLinkText.setText(link.getText());
                Glide.with(mContext).load(link.getStream()).into(ivLinkPicture);
                break;
        }
        switch (data.getSendState()) {
            case Constants.CHAT_ITEM_SENDING:
                chatItemProgress.setVisibility(View.VISIBLE);
                chatItemFail.setVisibility(View.GONE);
                break;
            case Constants.CHAT_ITEM_SEND_ERROR:
                chatItemProgress.setVisibility(View.GONE);
                chatItemFail.setVisibility(View.VISIBLE);
                break;
            case Constants.CHAT_ITEM_SEND_SUCCESS:
                chatItemProgress.setVisibility(View.GONE);
                chatItemFail.setVisibility(View.GONE);
                break;
        }
    }

    public void setItemLongClick() {
        chatItemContentImage.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickImage(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemContentText.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickText(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemLayoutContent.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickItem(v, (Integer) itemView.getTag());
                return true;
            }
        });
        chatItemLayoutFile.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onItemClickListener.onLongClickFile(v, (Integer) itemView.getTag());
                return true;
            }
        });
    }

    public void setItemClick() {
        chatItemContentImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onFileClick(v, (Integer) itemView.getTag());
            }
        });

        chatItemLayoutLink.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onLinkClick(v, (Integer) itemView.getTag());
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/base/BaseFragment.java
================================================
package com.rance.chatui.base;


import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

/**
 * 作者:Rance on 2016/11/18 15:19
 * 邮箱:rance935@163.com
 */
public class BaseFragment extends Fragment {
    public Activity mActivity;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mActivity = getActivity();
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mActivity = getActivity();
    }

    public void toastShow(int resId) {
        Toast.makeText(mActivity, resId, Toast.LENGTH_SHORT).show();
    }

    public void toastShow(String resId) {
        Toast.makeText(mActivity, resId, Toast.LENGTH_SHORT).show();
    }

    public ProgressDialog progressDialog;

    public ProgressDialog showProgressDialog() {
        progressDialog = new ProgressDialog(mActivity);
        progressDialog.setMessage("加载中");
        progressDialog.show();
        return progressDialog;
    }

    public ProgressDialog showProgressDialog(CharSequence message) {
        progressDialog = new ProgressDialog(mActivity);
        progressDialog.setMessage(message);
        progressDialog.show();
        return progressDialog;
    }

    public void dismissProgressDialog() {
        if (progressDialog != null && progressDialog.isShowing()) {
            // progressDialog.hide();会导致android.view.WindowLeaked
            progressDialog.dismiss();
        }
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/base/MyApplication.java
================================================
package com.rance.chatui.base;

import android.app.Application;
import android.content.Context;
import android.util.DisplayMetrics;

/**
 * 作者:Rance on 2016/12/20 16:49
 * 邮箱:rance935@163.com
 */
public class MyApplication extends Application {
    private static MyApplication mInstance;
    public static Context mContext;
    /**
     * 屏幕宽度
     */
    public static int screenWidth;
    /**
     * 屏幕高度
     */
    public static int screenHeight;
    /**
     * 屏幕密度
     */
    public static float screenDensity;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        mInstance = this;
        initScreenSize();
    }

    public static Context getInstance() {
        return mInstance;
    }

    /**
     * 初始化当前设备屏幕宽高
     */
    private void initScreenSize() {
        DisplayMetrics curMetrics = getApplicationContext().getResources().getDisplayMetrics();
        screenWidth = curMetrics.widthPixels;
        screenHeight = curMetrics.heightPixels;
        screenDensity = curMetrics.density;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/enity/FullImageInfo.java
================================================
package com.rance.chatui.enity;

/**
 * 作者:Rance on 2016/12/21 16:22
 * 邮箱:rance935@163.com
 */
public class FullImageInfo {
    private int locationX;
    private int locationY;
    private int width;
    private int height;
    private String imageUrl;

    public int getLocationX() {
        return locationX;
    }

    public void setLocationX(int locationX) {
        this.locationX = locationX;
    }

    public int getLocationY() {
        return locationY;
    }

    public void setLocationY(int locationY) {
        this.locationY = locationY;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/enity/IMContact.java
================================================
package com.rance.chatui.enity;

/**
 * Created by chengz
 *
 * @date 2017/8/3.
 */

public class IMContact {
    String name, phonenumber, surname;

    public IMContact(String name, String phonenumber) {
        this.name = name;
        this.phonenumber = phonenumber;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhonenumber() {
        return phonenumber;
    }

    public void setPhonenumber(String phonenumber) {
        this.phonenumber = phonenumber;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/enity/Link.java
================================================
package com.rance.chatui.enity;

/**
 * Created by chengz
 *
 * @date 2017/8/4.
 */

public class Link {
    String subject, text, stream, url;

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getStream() {
        return stream;
    }

    public void setStream(String stream) {
        this.stream = stream;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/enity/MessageInfo.java
================================================
package com.rance.chatui.enity;

/**
 * 作者:Rance on 2016/12/14 14:13
 * 邮箱:rance935@163.com
 */
public class MessageInfo {
    private int type;
    private String content;
    private String filepath;
    private int sendState;
    private String time;
    private String header;
    private long voiceTime;
    private String msgId;
    private String fileType;
    private Object object;
    private String mimeType;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public String getFileType() {
        return fileType;
    }

    public void setFileType(String fileType) {
        this.fileType = fileType;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getFilepath() {
        return filepath;
    }

    public void setFilepath(String filepath) {
        this.filepath = filepath;
    }

    public int getSendState() {
        return sendState;
    }

    public void setSendState(int sendState) {
        this.sendState = sendState;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public long getVoiceTime() {
        return voiceTime;
    }

    public void setVoiceTime(long voiceTime) {
        this.voiceTime = voiceTime;
    }

    public String getMsgId() {
        return msgId;
    }

    public void setMsgId(String msgId) {
        this.msgId = msgId;
    }

    public String getMimeType() {
        return mimeType;
    }

    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

    @Override
    public String toString() {
        return "MessageInfo{" +
                "type=" + type +
                ", content='" + content + '\'' +
                ", filepath='" + filepath + '\'' +
                ", sendState=" + sendState +
                ", time='" + time + '\'' +
                ", header='" + header + '\'' +
                ", mimeType='" + mimeType + '\'' +
                ", voiceTime=" + voiceTime +
                ", msgId='" + msgId + '\'' +
                ", fileType='" + fileType + '\'' +
                ", object=" + object +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/ui/activity/ContactActivity.java
================================================
package com.rance.chatui.ui.activity;

import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.rance.chatui.R;
import com.rance.chatui.adapter.ContactAdapter;
import com.rance.chatui.enity.IMContact;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.util.Constants;

import org.greenrobot.eventbus.EventBus;

import java.util.ArrayList;
import java.util.List;


public class ContactActivity extends AppCompatActivity implements ContactAdapter.OnContactClickListener {
    RecyclerView rvContact;

    private List<IMContact> imContactList;
    private final static int CODE_REQUEST_CONTACT = 0x222;
    private ContactAdapter mContactAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);
        rvContact = (RecyclerView) findViewById(R.id.rv_contact);
        setup();
        checkPermission();
    }

    private void setup() {
        rvContact.setLayoutManager(new LinearLayoutManager(this));
    }

    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}
                    , CODE_REQUEST_CONTACT);
        } else {
            readContacts();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case CODE_REQUEST_CONTACT:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts();
                } else {
                    Toast.makeText(this, "未设置读取联系人权限", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    private void readContacts() {
        imContactList = new ArrayList<>();
        Cursor cursor = null;
        try {
            Uri contactUri =ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
            cursor = this.getContentResolver().query(contactUri,
                    new String[]{"display_name", "sort_key", "contact_id","data1"},
                    null, null, "sort_key");
            String contactName;
            String contactNumber;
            while (cursor.moveToNext()) {
                contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                IMContact imContact = new IMContact(contactName, contactNumber);
                imContact.setSurname(contactName.substring(0, 1));
                if (contactName!=null)
                    imContactList.add(imContact);
            }
            cursor.close();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
        }

        loadData();
    }

    private void loadData() {
        mContactAdapter = new ContactAdapter(imContactList);
        mContactAdapter.setOnContactClickListener(this);
        rvContact.setAdapter(mContactAdapter);
    }

    @Override
    public void onContactClick(View view, final IMContact imContact) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Send to John Snow");

        View contentView = LayoutInflater.from(this).inflate(R.layout.dialog_contact, null);

        TextView tvSurname = (TextView) contentView.findViewById(R.id.tv_surname);
        tvSurname.setText(imContact.getSurname());
//        ((TextView)contentView.findViewById(R.id.tv_surname)).setText(imContact.getName().charAt(0));
        ((TextView)contentView.findViewById(R.id.tv_name)).setText(imContact.getName());
        ((TextView)contentView.findViewById(R.id.tv_phone)).setText("电话:" + imContact.getPhonenumber());

        builder.setView(contentView);
        builder.setPositiveButton("Send", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                MessageInfo messageInfo = new MessageInfo();
                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_CONTACT);
                messageInfo.setObject(imContact);
                EventBus.getDefault().post(messageInfo);
                dialog.dismiss();
                finish();
            }
        });

        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        builder.show();
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/ui/activity/FullImageActivity.java
================================================
package com.rance.chatui.ui.activity;

import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.bumptech.glide.Glide;
import com.rance.chatui.R;
import com.rance.chatui.enity.FullImageInfo;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;


/**
 * 作者:Rance on 2016/12/15 15:56
 * 邮箱:rance935@163.com
 */
public class FullImageActivity extends Activity {

    ImageView fullImage;
    LinearLayout fullLay;
    private int mLeft;
    private int mTop;
    private float mScaleX;
    private float mScaleY;
    private Drawable mBackground;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_full_image);
        fullImage = (ImageView) findViewById(R.id.full_image);
        fullLay = (LinearLayout) findViewById(R.id.full_lay);

        fullImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onImageClick();
            }
        });

        EventBus.getDefault().register(this);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) //在ui线程执行
    public void onDataSynEvent(final FullImageInfo fullImageInfo) {
        final int left = fullImageInfo.getLocationX();
        final int top = fullImageInfo.getLocationY();
        final int width = fullImageInfo.getWidth();
        final int height = fullImageInfo.getHeight();
        mBackground = new ColorDrawable(Color.BLACK);
        fullLay.setBackground(mBackground);
        fullImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                fullImage.getViewTreeObserver().removeOnPreDrawListener(this);
                int location[] = new int[2];
                fullImage.getLocationOnScreen(location);
                mLeft = left - location[0];
                mTop = top - location[1];
                mScaleX = width * 1.0f / fullImage.getWidth();
                mScaleY = height * 1.0f / fullImage.getHeight();
                activityEnterAnim();
                return true;
            }
        });
        Glide.with(this).load(fullImageInfo.getImageUrl()).into(fullImage);
    }

    private void activityEnterAnim() {
        fullImage.setPivotX(0);
        fullImage.setPivotY(0);
        fullImage.setScaleX(mScaleX);
        fullImage.setScaleY(mScaleY);
        fullImage.setTranslationX(mLeft);
        fullImage.setTranslationY(mTop);
        fullImage.animate().scaleX(1).scaleY(1).translationX(0).translationY(0).
                setDuration(500).setInterpolator(new DecelerateInterpolator()).start();
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
        objectAnimator.setInterpolator(new DecelerateInterpolator());
        objectAnimator.setDuration(500);
        objectAnimator.start();
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void activityExitAnim(Runnable runnable) {
        fullImage.setPivotX(0);
        fullImage.setPivotY(0);
        fullImage.animate().scaleX(mScaleX).scaleY(mScaleY).translationX(mLeft).translationY(mTop).
                withEndAction(runnable).
                setDuration(500).setInterpolator(new DecelerateInterpolator()).start();
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0);
        objectAnimator.setInterpolator(new DecelerateInterpolator());
        objectAnimator.setDuration(500);
        objectAnimator.start();
    }

    @Override
    public void onBackPressed() {
        activityExitAnim(new Runnable() {
            @Override
            public void run() {
                finish();
                overridePendingTransition(0, 0);
            }
        });
    }

    public void onImageClick() {
        activityExitAnim(new Runnable() {
            @Override
            public void run() {
                finish();
                overridePendingTransition(0, 0);
            }
        });
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/ui/activity/IMActivity.java
================================================
package com.rance.chatui.ui.activity;

import android.content.Intent;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.labo.kaji.relativepopupwindow.RelativePopupWindow;
import com.rance.chatui.R;
import com.rance.chatui.adapter.ChatAdapter;
import com.rance.chatui.adapter.CommonFragmentPagerAdapter;
import com.rance.chatui.enity.FullImageInfo;
import com.rance.chatui.enity.Link;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.ui.fragment.ChatEmotionFragment;
import com.rance.chatui.ui.fragment.ChatFunctionFragment;
import com.rance.chatui.util.Constants;
import com.rance.chatui.util.GlobalOnItemClickManagerUtils;
import com.rance.chatui.util.MediaManager;
import com.rance.chatui.util.MessageCenter;
import com.rance.chatui.widget.ChatContextMenu;
import com.rance.chatui.widget.EmotionInputDetector;
import com.rance.chatui.widget.NoScrollViewPager;
import com.rance.chatui.widget.StateButton;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.File;
import java.util.ArrayList;
import java.util.List;


/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class IMActivity extends AppCompatActivity {
    private static final String TAG = "IMActivity";
    RecyclerView chatList;
    ImageView emotionVoice;
    EditText editText;
    TextView voiceText;
    ImageView emotionButton;
    ImageView emotionAdd;
    StateButton emotionSend;
    NoScrollViewPager viewpager;
    RelativeLayout emotionLayout;

    private EmotionInputDetector mDetector;
    private ArrayList<Fragment> fragments;
    private ChatEmotionFragment chatEmotionFragment;
    private ChatFunctionFragment chatFunctionFragment;
    private CommonFragmentPagerAdapter adapter;

    private ChatAdapter chatAdapter;
    private LinearLayoutManager layoutManager;
    private List<MessageInfo> messageInfos;
    //录音相关
    int animationRes = 0;
    int res = 0;
    AnimationDrawable animationDrawable = null;
    private ImageView animView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewByIds();
        EventBus.getDefault().register(this);
        initWidget();
        handleIncomeAction();
    }

    private void findViewByIds() {
        chatList = (RecyclerView) findViewById(R.id.chat_list);
        emotionVoice = (ImageView) findViewById(R.id.emotion_voice);
        editText = (EditText) findViewById(R.id.edit_text);
        voiceText = (TextView) findViewById(R.id.voice_text);
        emotionButton = (ImageView) findViewById(R.id.emotion_button);
        emotionAdd = (ImageView) findViewById(R.id.emotion_add);
        emotionSend = (StateButton) findViewById(R.id.emotion_send);
        emotionLayout = (RelativeLayout) findViewById(R.id.emotion_layout);
        viewpager = (NoScrollViewPager) findViewById(R.id.viewpager);
    }

    private void handleIncomeAction() {
        Bundle bundle = getIntent().getExtras();
        if (bundle == null) {
            return;
        }

        MessageCenter.handleIncoming(bundle, getIntent().getType(), this);
    }

    private void initWidget() {
        fragments = new ArrayList<>();
        chatEmotionFragment = new ChatEmotionFragment();
        fragments.add(chatEmotionFragment);
        chatFunctionFragment = new ChatFunctionFragment();
        fragments.add(chatFunctionFragment);
        adapter = new CommonFragmentPagerAdapter(getSupportFragmentManager(), fragments);
        viewpager.setAdapter(adapter);
        viewpager.setCurrentItem(0);

        mDetector = EmotionInputDetector.with(this)
                .setEmotionView(emotionLayout)
                .setViewPager(viewpager)
                .bindToContent(chatList)
                .bindToEditText(editText)
                .bindToEmotionButton(emotionButton)
                .bindToAddButton(emotionAdd)
                .bindToSendButton(emotionSend)
                .bindToVoiceButton(emotionVoice)
                .bindToVoiceText(voiceText)
                .build();

        GlobalOnItemClickManagerUtils globalOnItemClickListener = GlobalOnItemClickManagerUtils.getInstance(this);
        globalOnItemClickListener.attachToEditText(editText);

        chatAdapter = new ChatAdapter(messageInfos);
        layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        chatList.setLayoutManager(layoutManager);
        chatList.setAdapter(chatAdapter);
        chatList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                switch (newState) {
                    case RecyclerView.SCROLL_STATE_IDLE:
                        chatAdapter.handler.removeCallbacksAndMessages(null);
                        chatAdapter.notifyDataSetChanged();
                        break;
                    case RecyclerView.SCROLL_STATE_DRAGGING:
                        chatAdapter.handler.removeCallbacksAndMessages(null);
                        mDetector.hideEmotionLayout(false);
                        mDetector.hideSoftInput();
                        break;
                    default:
                        break;
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
        chatAdapter.addItemClickListener(itemClickListener);
        LoadData();
    }

    /**
     * item点击事件
     */
    private ChatAdapter.onItemClickListener itemClickListener = new ChatAdapter.onItemClickListener() {
        @Override
        public void onHeaderClick(int position) {
            Toast.makeText(IMActivity.this, "onHeaderClick", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onImageClick(View view, int position) {
            int location[] = new int[2];
            view.getLocationOnScreen(location);
            FullImageInfo fullImageInfo = new FullImageInfo();
            fullImageInfo.setLocationX(location[0]);
            fullImageInfo.setLocationY(location[1]);
            fullImageInfo.setWidth(view.getWidth());
            fullImageInfo.setHeight(view.getHeight());
            fullImageInfo.setImageUrl(messageInfos.get(position).getFilepath());
            EventBus.getDefault().postSticky(fullImageInfo);
            startActivity(new Intent(IMActivity.this, FullImageActivity.class));
            overridePendingTransition(0, 0);
        }

        @Override
        public void onVoiceClick(final ImageView imageView, final int position) {
            if (animView != null) {
                animView.setImageResource(res);
                animView = null;
            }
            switch (messageInfos.get(position).getType()) {
                case 1:
                    animationRes = R.drawable.voice_left;
                    res = R.mipmap.icon_voice_left3;
                    break;
                case 2:
                    animationRes = R.drawable.voice_right;
                    res = R.mipmap.icon_voice_right3;
                    break;
            }
            animView = imageView;
            animView.setImageResource(animationRes);
            animationDrawable = (AnimationDrawable) imageView.getDrawable();
            animationDrawable.start();
            MediaManager.playSound(messageInfos.get(position).getFilepath(), new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    animView.setImageResource(res);
                }
            });
        }

        @Override
        public void onFileClick(View view, int position) {

            MessageInfo messageInfo = messageInfos.get(position);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            File file = new File(messageInfo.getFilepath());
            Uri fileUri = FileProvider.getUriForFile(IMActivity.this, Constants.AUTHORITY, file);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
            intent.setDataAndType(fileUri, messageInfo.getMimeType());
            startActivity(intent);
        }

        @Override
        public void onLinkClick(View view, int position) {
            MessageInfo messageInfo = messageInfos.get(position);
            Link link = (Link) messageInfo.getObject();
            Uri uri = Uri.parse(link.getUrl());
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(intent);
        }

        @Override
        public void onLongClickImage(View view, int position) {

            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));
//            chatContextMenu.setAnimationStyle();
            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,
                    RelativePopupWindow.HorizontalPosition.CENTER);

        }

        @Override
        public void onLongClickText(View view, int position) {
            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));
            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,
                    RelativePopupWindow.HorizontalPosition.CENTER);
        }

        @Override
        public void onLongClickItem(View view, int position) {
            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));
            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,
                    RelativePopupWindow.HorizontalPosition.CENTER);
        }

        @Override
        public void onLongClickFile(View view, int position) {
            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));
            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,
                    RelativePopupWindow.HorizontalPosition.CENTER);
        }

        @Override
        public void onLongClickLink(View view, int position) {
            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));
            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,
                    RelativePopupWindow.HorizontalPosition.CENTER);
        }
    };

    /**
     * 构造聊天数据
     */
    private void LoadData() {
        messageInfos = new ArrayList<>();

        MessageInfo messageInfo = new MessageInfo();
        messageInfo.setContent("你好,欢迎使用Rance的聊天界面框架");
        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_TEXT);
        messageInfo.setType(Constants.CHAT_ITEM_TYPE_LEFT);
        messageInfo.setHeader("http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg");
        messageInfos.add(messageInfo);

        MessageInfo messageInfo1 = new MessageInfo();
        messageInfo1.setFilepath("http://www.trueme.net/bb_midi/welcome.wav");
        messageInfo1.setVoiceTime(3000);
        messageInfo1.setFileType(Constants.CHAT_FILE_TYPE_VOICE);
        messageInfo1.setType(Constants.CHAT_ITEM_TYPE_RIGHT);
        messageInfo1.setSendState(Constants.CHAT_ITEM_SEND_SUCCESS);
        messageInfo1.setHeader("http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg");
        messageInfos.add(messageInfo1);

        MessageInfo messageInfo2 = new MessageInfo();
        messageInfo2.setFilepath("http://img4.imgtn.bdimg.com/it/u=1800788429,176707229&fm=21&gp=0.jpg");
        messageInfo2.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);
        messageInfo2.setType(Constants.CHAT_ITEM_TYPE_LEFT);
        messageInfo2.setHeader("http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg");
        messageInfos.add(messageInfo2);

        MessageInfo messageInfo3 = new MessageInfo();
        messageInfo3.setContent("[微笑][色][色][色]");
        messageInfo3.setFileType(Constants.CHAT_FILE_TYPE_TEXT);
        messageInfo3.setType(Constants.CHAT_ITEM_TYPE_RIGHT);
        messageInfo3.setSendState(Constants.CHAT_ITEM_SEND_ERROR);
        messageInfo3.setHeader("http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg");
        messageInfos.add(messageInfo3);

        chatAdapter.addAll(messageInfos);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void MessageEventBus(final MessageInfo messageInfo) {
        messageInfo.setHeader("http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg");
        messageInfo.setType(Constants.CHAT_ITEM_TYPE_RIGHT);
        messageInfo.setSendState(Constants.CHAT_ITEM_SENDING);
        messageInfos.add(messageInfo);
        chatAdapter.notifyItemInserted(messageInfos.size() - 1);
//        chatAdapter.add(messageInfo);
        chatList.scrollToPosition(chatAdapter.getItemCount() - 1);
        new Handler().postDelayed(new Runnable() {
            public void run() {
                messageInfo.setSendState(Constants.CHAT_ITEM_SEND_SUCCESS);
                chatAdapter.notifyDataSetChanged();
            }
        }, 2000);
        new Handler().postDelayed(new Runnable() {
            public void run() {
                MessageInfo message = new MessageInfo();
                message.setContent("这是模拟消息回复");
                message.setType(Constants.CHAT_ITEM_TYPE_LEFT);
                message.setFileType(Constants.CHAT_FILE_TYPE_TEXT);
                message.setHeader("http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg");
                messageInfos.add(message);
                chatAdapter.notifyItemInserted(messageInfos.size() - 1);
                chatList.scrollToPosition(chatAdapter.getItemCount() - 1);
            }
        }, 3000);
    }

    @Override
    public void onBackPressed() {
        if (!mDetector.interceptBackPress()) {
            super.onBackPressed();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().removeStickyEvent(this);
        EventBus.getDefault().unregister(this);
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/ui/fragment/ChatEmotionFragment.java
================================================
package com.rance.chatui.ui.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.LinearLayout;

import com.rance.chatui.R;
import com.rance.chatui.adapter.EmotionGridViewAdapter;
import com.rance.chatui.adapter.EmotionPagerAdapter;
import com.rance.chatui.base.BaseFragment;
import com.rance.chatui.base.MyApplication;
import com.rance.chatui.util.Utils;
import com.rance.chatui.util.EmotionUtils;
import com.rance.chatui.util.GlobalOnItemClickManagerUtils;
import com.rance.chatui.widget.IndicatorView;

import java.util.ArrayList;
import java.util.List;


/**
 * 作者:Rance on 2016/12/13 16:01
 * 邮箱:rance935@163.com
 */
public class ChatEmotionFragment extends BaseFragment {
    ViewPager fragmentChatVp;
    IndicatorView fragmentChatGroup;
    private View rootView;
    private EmotionPagerAdapter emotionPagerAdapter;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment_chat_emotion, container, false);
            fragmentChatVp = (ViewPager) rootView.findViewById(R.id.fragment_chat_vp);
            fragmentChatGroup = (IndicatorView) rootView.findViewById(R.id.fragment_chat_group);
            initWidget();
        }
        return rootView;
    }

    private void initWidget() {
        fragmentChatVp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            int oldPagerPos = 0;

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                fragmentChatGroup.playByStartPointToNext(oldPagerPos, position);
                oldPagerPos = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        initEmotion();
    }

    /**
     * 初始化表情面板
     * 思路:获取表情的总数,按每行存放7个表情,动态计算出每个表情所占的宽度大小(包含间距),
     *      而每个表情的高与宽应该是相等的,这里我们约定只存放3行
     *      每个面板最多存放7*3=21个表情,再减去一个删除键,即每个面板包含20个表情
     *      根据表情总数,循环创建多个容量为20的List,存放表情,对于大小不满20进行特殊
     *      处理即可。
     */
    private void initEmotion() {
        // 获取屏幕宽度
        int screenWidth = MyApplication.screenWidth;
        // item的间距
        int spacing = Utils.dp2px(getActivity(), 12);
        // 动态计算item的宽度和高度
        int itemWidth = (screenWidth - spacing * 8) / 7;
        //动态计算gridview的总高度
        int gvHeight = itemWidth * 3 + spacing * 6;

        List<GridView> emotionViews = new ArrayList<>();
        List<String> emotionNames = new ArrayList<>();
        // 遍历所有的表情的key
        for (String emojiName : EmotionUtils.EMOTION_STATIC_MAP.keySet()) {
            emotionNames.add(emojiName);
            // 每20个表情作为一组,同时添加到ViewPager对应的view集合中
            if (emotionNames.size() == 23) {
                GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);
                emotionViews.add(gv);
                // 添加完一组表情,重新创建一个表情名字集合
                emotionNames = new ArrayList<>();
            }
        }

        // 判断最后是否有不足23个表情的剩余情况
        if (emotionNames.size() > 0) {
            GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);
            emotionViews.add(gv);
        }

        //初始化指示器
        fragmentChatGroup.initIndicator(emotionViews.size());
        // 将多个GridView添加显示到ViewPager中
        emotionPagerAdapter = new EmotionPagerAdapter(emotionViews);
        fragmentChatVp.setAdapter(emotionPagerAdapter);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, gvHeight);
        fragmentChatVp.setLayoutParams(params);


    }

    /**
     * 创建显示表情的GridView
     */
    private GridView createEmotionGridView(List<String> emotionNames, int gvWidth, int padding, int itemWidth, int gvHeight) {
        // 创建GridView
        GridView gv = new GridView(getActivity());
        //设置点击背景透明
        gv.setSelector(android.R.color.transparent);
        //设置7列
        gv.setNumColumns(8);
        gv.setPadding(padding, padding, padding, padding);
        gv.setHorizontalSpacing(padding);
        gv.setVerticalSpacing(padding * 2);
        //设置GridView的宽高
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(gvWidth, gvHeight);
        gv.setLayoutParams(params);
        // 给GridView设置表情图片
        EmotionGridViewAdapter adapter = new EmotionGridViewAdapter(getActivity(), emotionNames, itemWidth);
        gv.setAdapter(adapter);
        //设置全局点击事件
        gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener());
        return gv;
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/ui/fragment/ChatFunctionFragment.java
================================================
package com.rance.chatui.ui.fragment;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.rance.chatui.R;
import com.rance.chatui.base.BaseFragment;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.ui.activity.ContactActivity;
import com.rance.chatui.util.Constants;
import com.rance.chatui.util.FileUtils;
import com.rance.chatui.util.PhotoUtils;

import org.greenrobot.eventbus.EventBus;

import java.io.File;


/**
 * 作者:Rance on 2016/12/13 16:01
 * 邮箱:rance935@163.com
 */
public class ChatFunctionFragment extends BaseFragment {
    private static final String TAG = "ChatFunctionFragment";
    private View rootView;
    private static final int CODE_TAKE_PHOTO = 0x111;
    private static final int CODE_CROP_PHOTO = 0xa2;
    private static final int REQUEST_CODE_PICK_IMAGE = 0xa3;
    private static final int REQUEST_CODE_PICK_FILE = 0xa4;
    private static final int CODE_REQUEST_CAMERA = 0xa5;
    private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 0xa6;
    private static final int MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE = 0xa7;
    private static final int MY_PERMISSIONS_REQUEST_CAMERACODE = 0xa8;

    private int output_X = 480;
    private int output_Y = 480;
    //    private File output;
//    private Uri imageUri;
    private File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/photo.jpg");
    private File fileCropUri = new File(Environment.getExternalStorageDirectory().getPath() + "/crop_photo.jpg");
    private Uri imageUri;
    private Uri cropImageUri;
    TextView tvCapture, tvAlbum, tvContact, tvCloud, tvFile, tvLocation;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment_chat_function, container, false);
            findViewByIds(rootView);
            setItemClick();
        }
        return rootView;
    }

    private void findViewByIds(View rootView) {
        tvCapture = (TextView) rootView.findViewById(R.id.chat_function_capture);
        tvAlbum = (TextView) rootView.findViewById(R.id.chat_function_album);
        tvContact = (TextView) rootView.findViewById(R.id.chat_function_contact);
        tvCloud = (TextView) rootView.findViewById(R.id.chat_function_cloud);
        tvFile = (TextView) rootView.findViewById(R.id.chat_function_file);
        tvLocation = (TextView) rootView.findViewById(R.id.chat_function_location);
    }

    private void autoObtainCameraPermission() {

        if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.CAMERA)) {
                Toast.makeText(mActivity, "您已拒绝过一次", Toast.LENGTH_SHORT).show();
            }
            ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CAMERACODE);
        } else {//有权限直接调用系统相机拍照
            imageUri = Uri.fromFile(fileUri);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                imageUri = FileProvider.getUriForFile(mActivity, Constants.AUTHORITY, fileUri);//通过FileProvider创建一个content类型的Uri
            PhotoUtils.takePicture(this, imageUri, CODE_TAKE_PHOTO);
        }
    }

    public void setItemClick() {
        tvCapture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                autoObtainCameraPermission();
            }
        });
        tvAlbum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getActivity(),
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(getActivity(),
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE);

                } else {
                    choosePhoto();
                }
            }
        });

        tvFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getActivity(),
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(getActivity(),
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE);
                } else {
                    chooseFile();
                }
            }
        });

        tvCloud.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        tvLocation.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        tvContact.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showContact();
            }
        });
    }

    private void showContact() {
        Intent intent = new Intent(mActivity, ContactActivity.class);
        startActivity(intent);
    }

    private void chooseFile() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("file/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        startActivityForResult(intent, REQUEST_CODE_PICK_FILE);
    }

    /**
     * 拍照
     */
    private void takePhoto() {
        imageUri = Uri.fromFile(fileUri);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            imageUri = FileProvider.getUriForFile(mActivity, Constants.AUTHORITY, fileUri);
            PhotoUtils.takePicture(this, imageUri, CODE_TAKE_PHOTO);
        }
    }

    /**
     * 从相册选取图片
     */
    private void choosePhoto() {
        /**
         * 打开选择图片的界面
         */
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");//相片类型
        startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);

    }

    public void onActivityResult(int req, int res, Intent data) {
        switch (req) {
            case CODE_TAKE_PHOTO:
                if (res == Activity.RESULT_OK) {
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setFilepath(fileUri.getAbsolutePath());
                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);
                    EventBus.getDefault().post(messageInfo);
                }

                break;
            case CODE_CROP_PHOTO:
                if (res == Activity.RESULT_OK) {
                    try {
                        MessageInfo messageInfo = new MessageInfo();
                        messageInfo.setFilepath(cropImageUri.getPath());
                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);
                        EventBus.getDefault().post(messageInfo);
                    } catch (Exception e) {
                    }
                } else {
                    Log.d(Constants.TAG, "失败");
                }

                break;
            case REQUEST_CODE_PICK_IMAGE:
                if (res == Activity.RESULT_OK) {
                    try {

                        Uri uri = data.getData();
                        MessageInfo messageInfo = new MessageInfo();
                        messageInfo.setFilepath(getImageRealPathFromURI(uri));
                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);
                        EventBus.getDefault().post(messageInfo);
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.d(Constants.TAG, e.getMessage());
                    }
                } else {
                    Log.d(Constants.TAG, "失败");
                }

                break;
            case REQUEST_CODE_PICK_FILE:
                if (res == Activity.RESULT_OK) {
                    try {
                        Uri uri = data.getData();
                        Log.e(TAG, "onActivityResult: ->" + uri.getPath());
                        MessageInfo messageInfo = new MessageInfo();
                        messageInfo.setFilepath(FileUtils.getFileAbsolutePath(mActivity, uri));
                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_FILE);
                        EventBus.getDefault().post(messageInfo);
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.d(Constants.TAG, e.getMessage());
                    }
                } else {
                    Log.d(Constants.TAG, "失败");
                }
                break;

            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_CALL_PHONE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    takePhoto();
                } else {
                    toastShow("请同意系统权限后继续");
                }
                break;
            case MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    choosePhoto();
                } else {
                    toastShow("请同意系统权限后继续");
                }
                break;
            case MY_PERMISSIONS_REQUEST_CAMERACODE:
                if (grantResults.length > 0 &&
                        grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    imageUri = Uri.fromFile(fileUri);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        PhotoUtils.takePicture(this, imageUri, CODE_CROP_PHOTO);
                    }
                }
                break;
        }
    }

    public String getImageRealPathFromURI(Uri contentUri) {

        //TODO upload file、image、voice then return url;
        String res = null;
        String[] proj = {MediaStore.Images.Media.DATA};
        Cursor cursor = getActivity().getContentResolver().query(contentUri, proj, null, null, null);
        if (cursor.moveToFirst()) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            res = cursor.getString(column_index);
        }
        cursor.close();
        return res;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/AudioRecorderUtils.java
================================================
package com.rance.chatui.util;

import android.content.Context;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;

import java.io.File;
import java.io.IOException;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class AudioRecorderUtils {

    //文件路径
    private String filePath;
    //文件夹路径
    private String FolderPath;

    private MediaRecorder mMediaRecorder;
    private final String TAG = "fan";
    public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;

    private OnAudioStatusUpdateListener audioStatusUpdateListener;

    /**
     * 文件存储默认sdcard/cadyd/record
     */
    public AudioRecorderUtils() {

        //默认保存路径为/sdcard/record/下
        this(Environment.getExternalStorageDirectory() + "/cadyd/record/");
    }

    public AudioRecorderUtils(String filePath) {

        File path = new File(filePath);
        if (!path.exists())
            path.mkdirs();

        this.FolderPath = filePath;
    }

    private long startTime;
    private long endTime;

    /**
     * 开始录音 使用amr格式
     * 录音文件
     *
     * @return
     */
    public void startRecord(Context context) {
        if (!CheckPermissionUtils.isHasPermission(context)) {
            audioStatusUpdateListener.onError();
            return;
        }
        // 开始录音
        /* ①Initial:实例化MediaRecorder对象 */
        if (mMediaRecorder == null)
            mMediaRecorder = new MediaRecorder();
        try {
            /* ②setAudioSource/setVedioSource */
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
            /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
            /*
             * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
             * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
             */
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

            filePath = FolderPath + Utils.getCurrentTime() + ".amr";
            /* ③准备 */
            mMediaRecorder.setOutputFile(filePath);
            mMediaRecorder.setMaxDuration(MAX_LENGTH);
            mMediaRecorder.prepare();
            /* ④开始 */
            mMediaRecorder.start();
            // AudioRecord audioRecord.
            /* 获取开始时间* */
            startTime = System.currentTimeMillis();
            updateMicStatus();
            Log.e("fan", "startTime" + startTime);
        } catch (IllegalStateException e) {
            audioStatusUpdateListener.onError();
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        } catch (IOException e) {
            audioStatusUpdateListener.onError();
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        }
    }

    /**
     * 停止录音
     */
    public long stopRecord() {
        if (mMediaRecorder == null)
            return 0L;
        endTime = System.currentTimeMillis();
        //设置后不会崩
        mMediaRecorder.setOnErrorListener(null);
        mMediaRecorder.setPreviewDisplay(null);
        try {
            mMediaRecorder.stop();
        } catch (IllegalStateException e) {
            Log.d("stopRecord", e.getMessage());
        } catch (RuntimeException e) {
            Log.d("stopRecord", e.getMessage());
        } catch (Exception e) {
            Log.d("stopRecord", e.getMessage());
        }
        mMediaRecorder.reset();
        mMediaRecorder.release();
        mMediaRecorder = null;
        long time = endTime - startTime;
        audioStatusUpdateListener.onStop(time, filePath);
        filePath = "";
        return endTime - startTime;
    }

    /**
     * 取消录音
     */
    public void cancelRecord() {
        if (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
        File file = new File(filePath);
        if (file.exists())
            file.delete();
        filePath = "";

    }

    private final Handler mHandler = new Handler();
    private Runnable mUpdateMicStatusTimer = new Runnable() {
        public void run() {
            updateMicStatus();
        }
    };


    private int BASE = 1;
    private int SPACE = 100;// 间隔取样时间

    public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
        this.audioStatusUpdateListener = audioStatusUpdateListener;
    }

    /**
     * 更新麦克状态
     */
    private void updateMicStatus() {

        if (mMediaRecorder != null) {
            double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
            double db = 0;// 分贝
            if (ratio > 1) {
                db = 20 * Math.log10(ratio);
                if (null != audioStatusUpdateListener) {
                    audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);
                }
            }
            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
        }
    }

    public interface OnAudioStatusUpdateListener {
        /**
         * 录音中...
         *
         * @param db   当前声音分贝
         * @param time 录音时长
         */
        public void onUpdate(double db, long time);

        /**
         * 停止录音
         *
         * @param time     录音时长
         * @param filePath 保存路径
         */
        public void onStop(long time, String filePath);

        /**
         * 录音失败
         */
        public void onError();
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/util/CheckPermissionUtils.java
================================================
package com.rance.chatui.util;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;

/**
 * 作者:Rance on 2016/12/14 11:09
 * 邮箱:rance935@163.com
 */
public class CheckPermissionUtils {
    // 音频获取源
    public static int audioSource = MediaRecorder.AudioSource.MIC;
    // 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    public static int sampleRateInHz = 44100;
    // 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
    public static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
    public static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    // 缓冲区字节大小
    public static int bufferSizeInBytes = 0;

    /**
     * 判断是是否有录音权限
     */
    public static boolean isHasPermission(final Context context){
        bufferSizeInBytes = 0;
        bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
                channelConfig, audioFormat);
        AudioRecord audioRecord =  new AudioRecord(audioSource, sampleRateInHz,
                channelConfig, audioFormat, bufferSizeInBytes);
        //开始录制音频
        try{
            // 防止某些手机崩溃,例如联想
            audioRecord.startRecording();
        }catch (IllegalStateException e){
            e.printStackTrace();
        }
        /**
         * 根据开始录音判断是否有录音权限
         */
        if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            new AlertDialog.Builder(context)
                    .setTitle("提示")
                    .setMessage("经检测语音权限未开启,设置方法:三方手机管理(应用宝、360)->安全->权限管理程序->应用程序->勾选语音权限。或去设置中心找到应用设置权限")
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    })
                    .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
                        }
                    })
                    .show();
            return false;
        }
        audioRecord.stop();
        audioRecord.release();
        audioRecord = null;
        return true;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/Constants.java
================================================
package com.rance.chatui.util;

/**
 * 作者:Rance on 2016/12/20 16:51
 * 邮箱:rance935@163.com
 */
public class Constants {
    public static final String TAG = "rance";
    public static final String AUTHORITY = "com.chatui.fileprovider";
    /** 0x001-接受消息  0x002-发送消息**/
    public static final int CHAT_ITEM_TYPE_LEFT = 0x001;
    public static final int CHAT_ITEM_TYPE_RIGHT = 0x002;
    /** 0x003-发送中  0x004-发送失败  0x005-发送成功**/
    public static final int CHAT_ITEM_SENDING = 0x003;
    public static final int CHAT_ITEM_SEND_ERROR = 0x004;
    public static final int CHAT_ITEM_SEND_SUCCESS = 0x005;

    public static final String CHAT_FILE_TYPE_TEXT = "text";
    public static final String CHAT_FILE_TYPE_FILE = "file";
    public static final String CHAT_FILE_TYPE_IMAGE = "image";
    public static final String CHAT_FILE_TYPE_VOICE = "voice";
    public static final String CHAT_FILE_TYPE_CONTACT = "contact";
    public static final String CHAT_FILE_TYPE_LINK = "LINK";
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/EmotionUtils.java
================================================

package com.rance.chatui.util;

import com.rance.chatui.R;

import java.util.LinkedHashMap;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 * 表情加载类,可自己添加多种表情,分别建立不同的map存放和不同的标志符即可
 */
public class EmotionUtils {

    /**
     * key-表情文字;
     * value-表情图片资源
     */
    public static LinkedHashMap<String, Integer> EMPTY_GIF_MAP;
    public static LinkedHashMap<String, Integer> EMOTION_STATIC_MAP;


    static {
        EMPTY_GIF_MAP = new LinkedHashMap<>();

        EMPTY_GIF_MAP.put("[微笑]", R.drawable.emotion_weixiao_gif);
        EMPTY_GIF_MAP.put("[撇嘴]", R.drawable.emotion_biezui_gif);
        EMPTY_GIF_MAP.put("[色]", R.drawable.emotion_se_gif);
        EMPTY_GIF_MAP.put("[发呆]", R.drawable.emotion_fadai_gif);
        EMPTY_GIF_MAP.put("[得意]", R.drawable.emotion_deyi_gif);
        EMPTY_GIF_MAP.put("[流泪]", R.drawable.emotion_liulei_gif);
        EMPTY_GIF_MAP.put("[害羞]", R.drawable.emotion_haixiu_gif);
        EMPTY_GIF_MAP.put("[闭嘴]", R.drawable.emotion_bizui_gif);
        EMPTY_GIF_MAP.put("[睡]", R.drawable.emotion_shui_gif);
        EMPTY_GIF_MAP.put("[大哭]", R.drawable.emotion_daku_gif);
        EMPTY_GIF_MAP.put("[尴尬]", R.drawable.emotion_ganga_gif);
        EMPTY_GIF_MAP.put("[发怒]", R.drawable.emotion_fanu_gif);
        EMPTY_GIF_MAP.put("[调皮]", R.drawable.emotion_tiaopi_gif);
        EMPTY_GIF_MAP.put("[呲牙]", R.drawable.emotion_ciya_gif);
        EMPTY_GIF_MAP.put("[惊讶]", R.drawable.emotion_jingya_gif);
        EMPTY_GIF_MAP.put("[难过]", R.drawable.emotion_nanguo_gif);
        EMPTY_GIF_MAP.put("[酷]", R.drawable.emotion_ku_gif);
        EMPTY_GIF_MAP.put("[冷汗]", R.drawable.emotion_lenghan_gif);
        EMPTY_GIF_MAP.put("[抓狂]", R.drawable.emotion_zhuakuang_gif);
        EMPTY_GIF_MAP.put("[吐]", R.drawable.emotion_tu_gif);
        EMPTY_GIF_MAP.put("[偷笑]", R.drawable.emotion_touxiao_gif);
        EMPTY_GIF_MAP.put("[可爱]", R.drawable.emotion_keai_gif);
        EMPTY_GIF_MAP.put("[白眼]", R.drawable.emotion_baiyan_gif);
        EMPTY_GIF_MAP.put("[傲慢]", R.drawable.emotion_aoman_gif);
        EMPTY_GIF_MAP.put("[饥饿]", R.drawable.emotion_jie_gif);
        EMPTY_GIF_MAP.put("[困]", R.drawable.emotion_kun_gif);
        EMPTY_GIF_MAP.put("[惊恐]", R.drawable.emotion_jingkong_gif);
        EMPTY_GIF_MAP.put("[流汗]", R.drawable.emotion_liuhan_gif);
        EMPTY_GIF_MAP.put("[憨笑]", R.drawable.emotion_hanxiao_gif);
        EMPTY_GIF_MAP.put("[大兵]", R.drawable.emotion_dabing_gif);
        EMPTY_GIF_MAP.put("[奋斗]", R.drawable.emotion_fendou_gif);
        EMPTY_GIF_MAP.put("[咒骂]", R.drawable.emotion_zouma_gif);
        EMPTY_GIF_MAP.put("[疑问]", R.drawable.emotion_yiwen_gif);
        EMPTY_GIF_MAP.put("[嘘]", R.drawable.emotion_xu_gif);
        EMPTY_GIF_MAP.put("[晕]", R.drawable.emotion_yun_gif);
        EMPTY_GIF_MAP.put("[折磨]", R.drawable.emotion_fakuang_gif);
        EMPTY_GIF_MAP.put("[衰]", R.drawable.emotion_shuai_gif);
        EMPTY_GIF_MAP.put("[骷髅]", R.drawable.emotion_kulou_gif);
        EMPTY_GIF_MAP.put("[敲打]", R.drawable.emotion_qiaoda_gif);
        EMPTY_GIF_MAP.put("[再见]", R.drawable.emotion_zaijian_gif);
        EMPTY_GIF_MAP.put("[擦汗]", R.drawable.emotion_cahan_gif);
        EMPTY_GIF_MAP.put("[抠鼻]", R.drawable.emotion_koubi_gif);
        EMPTY_GIF_MAP.put("[鼓掌]", R.drawable.emotion_guzhang_gif);
        EMPTY_GIF_MAP.put("[糗大了]", R.drawable.emotion_qiudale_gif);
        EMPTY_GIF_MAP.put("[坏笑]", R.drawable.emotion_huaixiao_gif);
        EMPTY_GIF_MAP.put("[左哼哼]", R.drawable.emotion_zuohengheng_gif);
        EMPTY_GIF_MAP.put("[右哼哼]", R.drawable.emotion_youhengheng_gif);
        EMPTY_GIF_MAP.put("[哈欠]", R.drawable.emotion_haqian_gif);
        EMPTY_GIF_MAP.put("[鄙视]", R.drawable.emotion_bishi_gif);
        EMPTY_GIF_MAP.put("[委屈]", R.drawable.emotion_weiqu_gif);
        EMPTY_GIF_MAP.put("[快哭了]", R.drawable.emotion_kuaikule_gif);
        EMPTY_GIF_MAP.put("[阴险]", R.drawable.emotion_yingxian_gif);
        EMPTY_GIF_MAP.put("[亲亲]", R.drawable.emotion_qinqin_gif);
        EMPTY_GIF_MAP.put("[吓]", R.drawable.emotion_xia_gif);
        EMPTY_GIF_MAP.put("[可怜]", R.drawable.emotion_kelian_gif);
        EMPTY_GIF_MAP.put("[菜刀]", R.drawable.emotion_caidao_gif);
        EMPTY_GIF_MAP.put("[西瓜]", R.drawable.emotion_xigua_gif);
        EMPTY_GIF_MAP.put("[啤酒]", R.drawable.emotion_pijiu_gif);
        EMPTY_GIF_MAP.put("[篮球]", R.drawable.emotion_lanqiu_gif);
        EMPTY_GIF_MAP.put("[乒乓]", R.drawable.emotion_pingpang_gif);
        EMPTY_GIF_MAP.put("[咖啡]", R.drawable.emotion_kafei_gif);
        EMPTY_GIF_MAP.put("[饭]", R.drawable.emotion_fan_gif);
        EMPTY_GIF_MAP.put("[猪头]", R.drawable.emotion_zhutou_gif);
        EMPTY_GIF_MAP.put("[玫瑰]", R.drawable.emotion_meigui_gif);
        EMPTY_GIF_MAP.put("[凋谢]", R.drawable.emotion_diaoxie_gif);
        EMPTY_GIF_MAP.put("[示爱]", R.drawable.emotion_shiai_gif);
        EMPTY_GIF_MAP.put("[爱心]", R.drawable.emotion_aixin_gif);
        EMPTY_GIF_MAP.put("[心碎]", R.drawable.emotion_xinsui_gif);
        EMPTY_GIF_MAP.put("[蛋糕]", R.drawable.emotion_dangao_gif);
        EMPTY_GIF_MAP.put("[闪电]", R.drawable.emotion_shandian_gif);
        EMPTY_GIF_MAP.put("[炸弹]", R.drawable.emotion_zhadan_gif);
        EMPTY_GIF_MAP.put("[刀]", R.drawable.emotion_dao_gif);
        EMPTY_GIF_MAP.put("[足球]", R.drawable.emotion_zhuqiu_gif);
        EMPTY_GIF_MAP.put("[瓢虫]", R.drawable.emotion_pachong_gif);
        EMPTY_GIF_MAP.put("[便便]", R.drawable.emotion_bianbian_gif);
        EMPTY_GIF_MAP.put("[月亮]", R.drawable.emotion_yueliang_gif);
        EMPTY_GIF_MAP.put("[太阳]", R.drawable.emotion_taiyang_gif);
        EMPTY_GIF_MAP.put("[礼物]", R.drawable.emotion_liwu_gif);
        EMPTY_GIF_MAP.put("[拥抱]", R.drawable.emotion_baobao_gif);
        EMPTY_GIF_MAP.put("[强]", R.drawable.emotion_qiang_gif);
        EMPTY_GIF_MAP.put("[弱]", R.drawable.emotion_ruo_gif);
        EMPTY_GIF_MAP.put("[握手]", R.drawable.emotion_woshou_gif);
        EMPTY_GIF_MAP.put("[胜利]", R.drawable.emotion_shengli_gif);
        EMPTY_GIF_MAP.put("[抱拳]", R.drawable.emotion_baoquan_gif);
        EMPTY_GIF_MAP.put("[勾引]", R.drawable.emotion_gouying_gif);
        EMPTY_GIF_MAP.put("[拳头]", R.drawable.emotion_quantou_gif);
        EMPTY_GIF_MAP.put("[差劲]", R.drawable.emotion_chajing_gif);
        EMPTY_GIF_MAP.put("[爱你]", R.drawable.emotion_aini_gif);
        EMPTY_GIF_MAP.put("[NO]", R.drawable.emotion_no_gif);
        EMPTY_GIF_MAP.put("[OK]", R.drawable.emotion_ok_gif);
        EMPTY_GIF_MAP.put("[爱情]", R.drawable.emotion_aiqing_gif);
        EMPTY_GIF_MAP.put("[飞吻]", R.drawable.emotion_feiwen_gif);
        EMPTY_GIF_MAP.put("[跳跳]", R.drawable.emotion_tiaotiao_gif);
        EMPTY_GIF_MAP.put("[发抖]", R.drawable.emotion_fadou_gif);
        EMPTY_GIF_MAP.put("[怄火]", R.drawable.emotion_ouhuo_gif);
        EMPTY_GIF_MAP.put("[转圈]", R.drawable.emotion_zhuanquan_gif);
        EMPTY_GIF_MAP.put("[磕头]", R.drawable.emotion_ketou_gif);
        EMPTY_GIF_MAP.put("[回头]", R.drawable.emotion_huitou_gif);
        EMPTY_GIF_MAP.put("[跳绳]", R.drawable.emotion_tiaosheng_gif);
        EMPTY_GIF_MAP.put("[挥手]", R.drawable.emotion_huishou_gif);
        EMPTY_GIF_MAP.put("[激动]", R.drawable.emotion_jidong_gif);
        EMPTY_GIF_MAP.put("[街舞]", R.drawable.emotion_jiewu_gif);
        EMPTY_GIF_MAP.put("[献吻]", R.drawable.emotion_xianwen_gif);
        EMPTY_GIF_MAP.put("[左太极]", R.drawable.emotion_zuotaiji_gif);
        EMPTY_GIF_MAP.put("[右太极]", R.drawable.emotion_youtaiji_gif);
        EMPTY_GIF_MAP.put("[双喜]", R.drawable.emotion_shuangxi_gif);
        EMPTY_GIF_MAP.put("[鞭炮]", R.drawable.emotion_bianpao_gif);
        EMPTY_GIF_MAP.put("[灯笼]", R.drawable.emotion_denglong_gif);
        EMPTY_GIF_MAP.put("[发财]", R.drawable.emotion_facai_gif);
        EMPTY_GIF_MAP.put("[K歌]", R.drawable.emotion_kge_gif);
        EMPTY_GIF_MAP.put("[购物]", R.drawable.emotion_gouwu_gif);
        EMPTY_GIF_MAP.put("[邮件]", R.drawable.emotion_youjian_gif);
        EMPTY_GIF_MAP.put("[帅]", R.drawable.emotion_dashuai_gif);
        EMPTY_GIF_MAP.put("[喝彩]", R.drawable.emotion_hecai_gif);
        EMPTY_GIF_MAP.put("[祈祷]", R.drawable.emotion_qidao_gif);
        EMPTY_GIF_MAP.put("[爆筋]", R.drawable.emotion_baojing_gif);
        EMPTY_GIF_MAP.put("[棒棒糖]", R.drawable.emotion_bangbangtang_gif);
        EMPTY_GIF_MAP.put("[喝奶]", R.drawable.emotion_henai_gif);
        EMPTY_GIF_MAP.put("[下面]", R.drawable.emotion_xiamian_gif);
        EMPTY_GIF_MAP.put("[香蕉]", R.drawable.emotion_xiangjiao_gif);
        EMPTY_GIF_MAP.put("[飞机]", R.drawable.emotion_feiji_gif);
        EMPTY_GIF_MAP.put("[开车]", R.drawable.emotion_kaiche_gif);
        EMPTY_GIF_MAP.put("[左车头]", R.drawable.emotion_zuochetou_gif);
        EMPTY_GIF_MAP.put("[车厢]", R.drawable.emotion_chexiang_gif);
        EMPTY_GIF_MAP.put("[右车头]", R.drawable.emotion_youchexiang_gif);
        EMPTY_GIF_MAP.put("[多云]", R.drawable.emotion_duoyun_gif);
        EMPTY_GIF_MAP.put("[下雨]", R.drawable.emotion_xiayu_gif);
        EMPTY_GIF_MAP.put("[钞票]", R.drawable.emotion_chaopiao_gif);
        EMPTY_GIF_MAP.put("[熊猫]", R.drawable.emotion_xiongmao_gif);
        EMPTY_GIF_MAP.put("[灯泡]", R.drawable.emotion_dengpao_gif);
        EMPTY_GIF_MAP.put("[风车]", R.drawable.emotion_fengche_gif);
        EMPTY_GIF_MAP.put("[闹钟]", R.drawable.emotion_naozhong_gif);
        EMPTY_GIF_MAP.put("[打伞]", R.drawable.emotion_dashan_gif);
        EMPTY_GIF_MAP.put("[彩球]", R.drawable.emotion_caiqiu_gif);
        EMPTY_GIF_MAP.put("[钻戒]", R.drawable.emotion_zhuanjie_gif);
        EMPTY_GIF_MAP.put("[沙发]", R.drawable.emotion_shafa_gif);
        EMPTY_GIF_MAP.put("[纸巾]", R.drawable.emotion_zhijing_gif);
        EMPTY_GIF_MAP.put("[药]", R.drawable.emotion_yao_gif);
        EMPTY_GIF_MAP.put("[手枪]", R.drawable.emotion_shouqiang_gif);
        EMPTY_GIF_MAP.put("[青蛙]", R.drawable.emotion_qingwa_gif);

        EMOTION_STATIC_MAP = new LinkedHashMap<>();

        EMOTION_STATIC_MAP.put("[微笑]", R.drawable.emotion_weixiao);
        EMOTION_STATIC_MAP.put("[撇嘴]", R.drawable.emotion_biezui);
        EMOTION_STATIC_MAP.put("[色]", R.drawable.emotion_se);
        EMOTION_STATIC_MAP.put("[发呆]", R.drawable.emotion_fadai);
        EMOTION_STATIC_MAP.put("[得意]", R.drawable.emotion_deyi);
        EMOTION_STATIC_MAP.put("[流泪]", R.drawable.emotion_liulei);
        EMOTION_STATIC_MAP.put("[害羞]", R.drawable.emotion_haixiu);
        EMOTION_STATIC_MAP.put("[闭嘴]", R.drawable.emotion_bizui);
        EMOTION_STATIC_MAP.put("[睡]", R.drawable.emotion_shui);
        EMOTION_STATIC_MAP.put("[大哭]", R.drawable.emotion_daku);
        EMOTION_STATIC_MAP.put("[尴尬]", R.drawable.emotion_ganga);
        EMOTION_STATIC_MAP.put("[发怒]", R.drawable.emotion_fanu);
        EMOTION_STATIC_MAP.put("[调皮]", R.drawable.emotion_tiaopi);
        EMOTION_STATIC_MAP.put("[呲牙]", R.drawable.emotion_ciya);
        EMOTION_STATIC_MAP.put("[惊讶]", R.drawable.emotion_jingya);
        EMOTION_STATIC_MAP.put("[难过]", R.drawable.emotion_nanguo);
        EMOTION_STATIC_MAP.put("[酷]", R.drawable.emotion_ku);
        EMOTION_STATIC_MAP.put("[冷汗]", R.drawable.emotion_lenghan);
        EMOTION_STATIC_MAP.put("[抓狂]", R.drawable.emotion_zhuakuang);
        EMOTION_STATIC_MAP.put("[吐]", R.drawable.emotion_tu);
        EMOTION_STATIC_MAP.put("[偷笑]", R.drawable.emotion_touxiao);
        EMOTION_STATIC_MAP.put("[可爱]", R.drawable.emotion_keai);
        EMOTION_STATIC_MAP.put("[白眼]", R.drawable.emotion_baiyan);
        EMOTION_STATIC_MAP.put("[傲慢]", R.drawable.emotion_aoman);
        EMOTION_STATIC_MAP.put("[饥饿]", R.drawable.emotion_jie);
        EMOTION_STATIC_MAP.put("[困]", R.drawable.emotion_kun);
        EMOTION_STATIC_MAP.put("[惊恐]", R.drawable.emotion_jingkong);
        EMOTION_STATIC_MAP.put("[流汗]", R.drawable.emotion_liuhan);
        EMOTION_STATIC_MAP.put("[憨笑]", R.drawable.emotion_hanxiao);
        EMOTION_STATIC_MAP.put("[大兵]", R.drawable.emotion_dabing);
        EMOTION_STATIC_MAP.put("[奋斗]", R.drawable.emotion_fendou);
        EMOTION_STATIC_MAP.put("[咒骂]", R.drawable.emotion_zouma);
        EMOTION_STATIC_MAP.put("[疑问]", R.drawable.emotion_yiwen);
        EMOTION_STATIC_MAP.put("[嘘]", R.drawable.emotion_xu);
        EMOTION_STATIC_MAP.put("[晕]", R.drawable.emotion_yun);
        EMOTION_STATIC_MAP.put("[折磨]", R.drawable.emotion_fakuang);
        EMOTION_STATIC_MAP.put("[衰]", R.drawable.emotion_shuai);
        EMOTION_STATIC_MAP.put("[骷髅]", R.drawable.emotion_kulou);
        EMOTION_STATIC_MAP.put("[敲打]", R.drawable.emotion_qiaoda);
        EMOTION_STATIC_MAP.put("[再见]", R.drawable.emotion_zaijian);
        EMOTION_STATIC_MAP.put("[擦汗]", R.drawable.emotion_cahan);
        EMOTION_STATIC_MAP.put("[抠鼻]", R.drawable.emotion_koubi);
        EMOTION_STATIC_MAP.put("[鼓掌]", R.drawable.emotion_guzhang);
        EMOTION_STATIC_MAP.put("[糗大了]", R.drawable.emotion_qiudale);
        EMOTION_STATIC_MAP.put("[坏笑]", R.drawable.emotion_huaixiao);
        EMOTION_STATIC_MAP.put("[左哼哼]", R.drawable.emotion_zuohengheng);
        EMOTION_STATIC_MAP.put("[右哼哼]", R.drawable.emotion_youhengheng);
        EMOTION_STATIC_MAP.put("[哈欠]", R.drawable.emotion_haqian);
        EMOTION_STATIC_MAP.put("[鄙视]", R.drawable.emotion_bishi);
        EMOTION_STATIC_MAP.put("[委屈]", R.drawable.emotion_weiqu);
        EMOTION_STATIC_MAP.put("[快哭了]", R.drawable.emotion_kuaikule);
        EMOTION_STATIC_MAP.put("[阴险]", R.drawable.emotion_yingxian);
        EMOTION_STATIC_MAP.put("[亲亲]", R.drawable.emotion_qinqin);
        EMOTION_STATIC_MAP.put("[吓]", R.drawable.emotion_xia);
        EMOTION_STATIC_MAP.put("[可怜]", R.drawable.emotion_kelian);
        EMOTION_STATIC_MAP.put("[菜刀]", R.drawable.emotion_caidao);
        EMOTION_STATIC_MAP.put("[西瓜]", R.drawable.emotion_xigua);
        EMOTION_STATIC_MAP.put("[啤酒]", R.drawable.emotion_pijiu);
        EMOTION_STATIC_MAP.put("[篮球]", R.drawable.emotion_lanqiu);
        EMOTION_STATIC_MAP.put("[乒乓]", R.drawable.emotion_pingpang);
        EMOTION_STATIC_MAP.put("[咖啡]", R.drawable.emotion_kafei);
        EMOTION_STATIC_MAP.put("[饭]", R.drawable.emotion_fan);
        EMOTION_STATIC_MAP.put("[猪头]", R.drawable.emotion_zhutou);
        EMOTION_STATIC_MAP.put("[玫瑰]", R.drawable.emotion_meigui);
        EMOTION_STATIC_MAP.put("[凋谢]", R.drawable.emotion_diaoxie);
        EMOTION_STATIC_MAP.put("[示爱]", R.drawable.emotion_shiai);
        EMOTION_STATIC_MAP.put("[爱心]", R.drawable.emotion_aixin);
        EMOTION_STATIC_MAP.put("[心碎]", R.drawable.emotion_xinsui);
        EMOTION_STATIC_MAP.put("[蛋糕]", R.drawable.emotion_dangao);
        EMOTION_STATIC_MAP.put("[闪电]", R.drawable.emotion_shandian);
        EMOTION_STATIC_MAP.put("[炸弹]", R.drawable.emotion_zhadan);
        EMOTION_STATIC_MAP.put("[刀]", R.drawable.emotion_dao);
        EMOTION_STATIC_MAP.put("[足球]", R.drawable.emotion_zhuqiu);
        EMOTION_STATIC_MAP.put("[瓢虫]", R.drawable.emotion_pachong);
        EMOTION_STATIC_MAP.put("[便便]", R.drawable.emotion_bianbian);
        EMOTION_STATIC_MAP.put("[月亮]", R.drawable.emotion_yueliang);
        EMOTION_STATIC_MAP.put("[太阳]", R.drawable.emotion_taiyang);
        EMOTION_STATIC_MAP.put("[礼物]", R.drawable.emotion_liwu);
        EMOTION_STATIC_MAP.put("[拥抱]", R.drawable.emotion_baobao);
        EMOTION_STATIC_MAP.put("[强]", R.drawable.emotion_qiang);
        EMOTION_STATIC_MAP.put("[弱]", R.drawable.emotion_ruo);
        EMOTION_STATIC_MAP.put("[握手]", R.drawable.emotion_woshou);
        EMOTION_STATIC_MAP.put("[胜利]", R.drawable.emotion_shengli);
        EMOTION_STATIC_MAP.put("[抱拳]", R.drawable.emotion_baoquan);
        EMOTION_STATIC_MAP.put("[勾引]", R.drawable.emotion_gouying);
        EMOTION_STATIC_MAP.put("[拳头]", R.drawable.emotion_quantou);
        EMOTION_STATIC_MAP.put("[差劲]", R.drawable.emotion_chajing);
        EMOTION_STATIC_MAP.put("[爱你]", R.drawable.emotion_aini);
        EMOTION_STATIC_MAP.put("[NO]", R.drawable.emotion_no);
        EMOTION_STATIC_MAP.put("[OK]", R.drawable.emotion_ok);
        EMOTION_STATIC_MAP.put("[爱情]", R.drawable.emotion_aiqing);
        EMOTION_STATIC_MAP.put("[飞吻]", R.drawable.emotion_feiwen);
        EMOTION_STATIC_MAP.put("[跳跳]", R.drawable.emotion_tiaotiao);
        EMOTION_STATIC_MAP.put("[发抖]", R.drawable.emotion_fadou);
        EMOTION_STATIC_MAP.put("[怄火]", R.drawable.emotion_ouhuo);
        EMOTION_STATIC_MAP.put("[转圈]", R.drawable.emotion_zhuanquan);
        EMOTION_STATIC_MAP.put("[磕头]", R.drawable.emotion_ketou);
        EMOTION_STATIC_MAP.put("[回头]", R.drawable.emotion_huitou);
        EMOTION_STATIC_MAP.put("[跳绳]", R.drawable.emotion_tiaosheng);
        EMOTION_STATIC_MAP.put("[挥手]", R.drawable.emotion_huishou);
        EMOTION_STATIC_MAP.put("[激动]", R.drawable.emotion_jidong);
        EMOTION_STATIC_MAP.put("[街舞]", R.drawable.emotion_jiewu);
        EMOTION_STATIC_MAP.put("[献吻]", R.drawable.emotion_xianwen);
        EMOTION_STATIC_MAP.put("[左太极]", R.drawable.emotion_zuotaiji);
        EMOTION_STATIC_MAP.put("[右太极]", R.drawable.emotion_youtaiji);
        EMOTION_STATIC_MAP.put("[双喜]", R.drawable.emotion_shuangxi);
        EMOTION_STATIC_MAP.put("[鞭炮]", R.drawable.emotion_bianpao);
        EMOTION_STATIC_MAP.put("[灯笼]", R.drawable.emotion_denglong);
        EMOTION_STATIC_MAP.put("[发财]", R.drawable.emotion_facai);
        EMOTION_STATIC_MAP.put("[K歌]", R.drawable.emotion_kge);
        EMOTION_STATIC_MAP.put("[购物]", R.drawable.emotion_gouwu);
        EMOTION_STATIC_MAP.put("[邮件]", R.drawable.emotion_youjian);
        EMOTION_STATIC_MAP.put("[帅]", R.drawable.emotion_dashuai);
        EMOTION_STATIC_MAP.put("[喝彩]", R.drawable.emotion_hecai);
        EMOTION_STATIC_MAP.put("[祈祷]", R.drawable.emotion_qidao);
        EMOTION_STATIC_MAP.put("[爆筋]", R.drawable.emotion_baojing);
        EMOTION_STATIC_MAP.put("[棒棒糖]", R.drawable.emotion_bangbangtang);
        EMOTION_STATIC_MAP.put("[喝奶]", R.drawable.emotion_henai);
        EMOTION_STATIC_MAP.put("[下面]", R.drawable.emotion_xiamian);
        EMOTION_STATIC_MAP.put("[香蕉]", R.drawable.emotion_xiangjiao);
        EMOTION_STATIC_MAP.put("[飞机]", R.drawable.emotion_feiji);
        EMOTION_STATIC_MAP.put("[开车]", R.drawable.emotion_kaiche);
        EMOTION_STATIC_MAP.put("[左车头]", R.drawable.emotion_zuochetou);
        EMOTION_STATIC_MAP.put("[车厢]", R.drawable.emotion_chexiang);
        EMOTION_STATIC_MAP.put("[右车头]", R.drawable.emotion_youchexiang);
        EMOTION_STATIC_MAP.put("[多云]", R.drawable.emotion_duoyun);
        EMOTION_STATIC_MAP.put("[下雨]", R.drawable.emotion_xiayu);
        EMOTION_STATIC_MAP.put("[钞票]", R.drawable.emotion_chaopiao);
        EMOTION_STATIC_MAP.put("[熊猫]", R.drawable.emotion_xiongmao);
        EMOTION_STATIC_MAP.put("[灯泡]", R.drawable.emotion_dengpao);
        EMOTION_STATIC_MAP.put("[风车]", R.drawable.emotion_fengche);
        EMOTION_STATIC_MAP.put("[闹钟]", R.drawable.emotion_naozhong);
        EMOTION_STATIC_MAP.put("[打伞]", R.drawable.emotion_dashan);
        EMOTION_STATIC_MAP.put("[彩球]", R.drawable.emotion_caiqiu);
        EMOTION_STATIC_MAP.put("[钻戒]", R.drawable.emotion_zhuanjie);
        EMOTION_STATIC_MAP.put("[沙发]", R.drawable.emotion_shafa);
        EMOTION_STATIC_MAP.put("[纸巾]", R.drawable.emotion_zhijing);
        EMOTION_STATIC_MAP.put("[药]", R.drawable.emotion_yao);
        EMOTION_STATIC_MAP.put("[手枪]", R.drawable.emotion_shouqiang);
        EMOTION_STATIC_MAP.put("[青蛙]", R.drawable.emotion_qingwa);
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/FileUtils.java
================================================
package com.rance.chatui.util;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.text.DecimalFormat;

/**
 * Created by chengz
 *
 * @date 2017/8/2.
 */

public class FileUtils {
    /**
     * 根据Uri获取文件的绝对路径,解决Android4.4以上版本Uri转换
     *
     * @param context
     * @param fileUri
     */
    @TargetApi(19)
    public static String getFileAbsolutePath(Activity context, Uri fileUri) {
        if (context == null || fileUri == null)
            return null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) {
            if (isExternalStorageDocument(fileUri)) {
                String docId = DocumentsContract.getDocumentId(fileUri);
                String[] split = docId.split(":");
                String type = split[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            } else if (isDownloadsDocument(fileUri)) {
                String id = DocumentsContract.getDocumentId(fileUri);
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            } else if (isMediaDocument(fileUri)) {
                String docId = DocumentsContract.getDocumentId(fileUri);
                String[] split = docId.split(":");
                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;
                }
                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = new String[] { split[1] };
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        } // MediaStore (and general)
        else if ("content".equalsIgnoreCase(fileUri.getScheme())) {
            // Return the remote address
            if (isGooglePhotosUri(fileUri))
                return fileUri.getLastPathSegment();
            return getDataColumn(context, fileUri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(fileUri.getScheme())) {
            return fileUri.getPath();
        }
        return null;
    }

    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        String[] projection = { MediaStore.Images.Media.DATA };
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }

    public static String getFileSize(String filePath) throws Exception {
        File file = new File(filePath);
        return getFileSize(file);
    }

    public static String getFileSize(File file) throws Exception {
        long size = 0;
        if (file.exists()) {
            FileInputStream fis = null;
            fis = new FileInputStream(file);
            size = fis.available();
        } else {
            file.createNewFile();
            Log.e("获取文件大小", "文件不存在!");
        }
        return formatFileSize(size);
    }

    public static String getExtensionName(String filePath) throws Exception {
        File file = new File(filePath);
        return getExtensionName(file);
    }

    public static String getExtensionName(File file) {
        String fileName= file.getName();
        return fileName.substring(fileName.lastIndexOf(".")+1);
    }

    public static String getFileName(String filePath) {
        File file = new File(filePath);
        return file.getName();
    }

    private static String formatFileSize(long fileS)
    {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize="0B";
        if(fileS==0){
            return wrongSize;
        }
        if (fileS < 1024){
            fileSizeString = df.format((double) fileS) + "B";
        }
        else if (fileS < 1048576){
            fileSizeString = df.format((double) fileS / 1024) + "KB";
        }
        else if (fileS < 1073741824){
            fileSizeString = df.format((double) fileS / 1048576) + "MB";
        }
        else{
            fileSizeString = df.format((double) fileS / 1073741824) + "GB";
        }
        return fileSizeString;
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/GifOpenHelper.java
================================================
package com.rance.chatui.util;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;

import java.io.InputStream;
import java.util.Vector;

//Handler for read & extract Bitmap from *.gif
public class GifOpenHelper {

	// to store *.gif data, Bitmap & delay
	class GifFrame {
		// to access image & delay w/o interface
		public Bitmap image;
		public int delay;

		public GifFrame(Bitmap im, int del) {
			image = im;
			delay = del;
		}

	}

	// to define some error type
	public static final int STATUS_OK = 0;
	public static final int STATUS_FORMAT_ERROR = 1;
	public static final int STATUS_OPEN_ERROR = 2;

	protected int status;

	protected InputStream in;

	protected int width; // full image width
	protected int height; // full image height
	protected boolean gctFlag; // global color table used
	protected int gctSize; // size of global color table
	protected int loopCount = 1; // iterations; 0 = repeat forever

	protected int[] gct; // global color table
	protected int[] lct; // local color table
	protected int[] act; // active color table

	protected int bgIndex; // background color index
	protected int bgColor; // background color
	protected int lastBgColor; // previous bg color
	protected int pixelAspect; // pixel aspect ratio

	protected boolean lctFlag; // local color table flag
	protected boolean interlace; // interlace flag
	protected int lctSize; // local color table size

	protected int ix, iy, iw, ih; // current image rectangle
	protected int lrx, lry, lrw, lrh;
	protected Bitmap image; // current frame
	protected Bitmap lastImage; // previous frame
	protected int frameindex = 0;

	public int getFrameindex() {
		return frameindex;
	}

	public void setFrameindex(int frameindex) {
		this.frameindex = frameindex;
		if (frameindex > frames.size() - 1) {
			frameindex = 0;
		}
	}

	protected byte[] block = new byte[256]; // current data block
	protected int blockSize = 0; // block size

	// last graphic control extension info
	protected int dispose = 0;
	// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
	protected int lastDispose = 0;
	protected boolean transparency = false; // use transparent color
	protected int delay = 0; // delay in milliseconds
	protected int transIndex; // transparent color index

	protected static final int MaxStackSize = 4096;
	// max decoder pixel stack size

	// LZW decoder working arrays
	protected short[] prefix;
	protected byte[] suffix;
	protected byte[] pixelStack;
	protected byte[] pixels;

	protected Vector<GifFrame> frames; // frames read from current file
	protected int frameCount;

	// to get its Width / Height
	public int getWidth() {
		return width;
	}

	public int getHeigh() {
		return height;
	}

	/**
	 * Gets display duration for specified frame.
	 * 
	 * @param n
	 *            int index of frame
	 * @return delay in milliseconds
	 */
	public int getDelay(int n) {
		delay = -1;
		if ((n >= 0) && (n < frameCount)) {
			delay = ((GifFrame) frames.elementAt(n)).delay;
		}
		return delay;
	}

	public int getFrameCount() {
		return frameCount;
	}

	public Bitmap getImage() {
		return getFrame(0);
	}

	public int getLoopCount() {
		return loopCount;
	}

	protected void setPixels() {
		int[] dest = new int[width * height];
		// fill in starting image contents based on last image's dispose code
		if (lastDispose > 0) {
			if (lastDispose == 3) {
				// use image before last
				int n = frameCount - 2;
				if (n > 0) {
					lastImage = getFrame(n - 1);
				} else {
					lastImage = null;
				}
			}
			if (lastImage != null) {
				lastImage.getPixels(dest, 0, width, 0, 0, width, height);
				// copy pixels
				if (lastDispose == 2) {
					// fill last image rect area with background color
					int c = 0;
					if (!transparency) {
						c = lastBgColor;
					}
					for (int i = 0; i < lrh; i++) {
						int n1 = (lry + i) * width + lrx;
						int n2 = n1 + lrw;
						for (int k = n1; k < n2; k++) {
							dest[k] = c;
						}
					}
				}
			}
		}

		// copy each source line to the appropriate place in the destination
		int pass = 1;
		int inc = 8;
		int iline = 0;
		for (int i = 0; i < ih; i++) {
			int line = i;
			if (interlace) {
				if (iline >= ih) {
					pass++;
					switch (pass) {
					case 2:
						iline = 4;
						break;
					case 3:
						iline = 2;
						inc = 4;
						break;
					case 4:
						iline = 1;
						inc = 2;
					}
				}
				line = iline;
				iline += inc;
			}
			line += iy;
			if (line < height) {
				int k = line * width;
				int dx = k + ix; // start of line in dest
				int dlim = dx + iw; // end of dest line
				if ((k + width) < dlim) {
					dlim = k + width; // past dest edge
				}
				int sx = i * iw; // start of line in source
				while (dx < dlim) {
					// map color and insert in destination
					int index = ((int) pixels[sx++]) & 0xff;
					int c = act[index];
					if (c != 0) {
						dest[dx] = c;
					}
					dx++;
				}
			}
		}
		image = Bitmap.createBitmap(dest, width, height, Config.ARGB_4444);
	}

	public Bitmap getFrame(int n) {
		Bitmap im = null;
		if ((n >= 0) && (n < frameCount)) {
			im = ((GifFrame) frames.elementAt(n)).image;
		}
		return im;
	}

	public Bitmap nextBitmap() {
		frameindex++;
		if (frameindex > frames.size() - 1) {
			frameindex = 0;
		}
		return ((GifFrame) frames.elementAt(frameindex)).image;
	}

	public int nextDelay() {
		return ((GifFrame) frames.elementAt(frameindex)).delay;
	}

	// to read & parse all *.gif stream
	public int read(InputStream is) {
		init();
		if (is != null) {
			in = is;

			readHeader();
			if (!err()) {
				readContents();
				if (frameCount < 0) {
					status = STATUS_FORMAT_ERROR;
				}
			}
		} else {
			status = STATUS_OPEN_ERROR;
		}
		try {
			is.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return status;
	}

	protected void decodeImageData() {
		int NullCode = -1;
		int npix = iw * ih;
		int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;

		if ((pixels == null) || (pixels.length < npix)) {
			pixels = new byte[npix]; // allocate new pixel array
		}
		if (prefix == null) {
			prefix = new short[MaxStackSize];
		}
		if (suffix == null) {
			suffix = new byte[MaxStackSize];
		}
		if (pixelStack == null) {
			pixelStack = new byte[MaxStackSize + 1];
		}
		// Initialize GIF data stream decoder.
		data_size = read();
		clear = 1 << data_size;
		end_of_information = clear + 1;
		available = clear + 2;
		old_code = NullCode;
		code_size = data_size + 1;
		code_mask = (1 << code_size) - 1;
		for (code = 0; code < clear; code++) {
			prefix[code] = 0;
			suffix[code] = (byte) code;
		}

		// Decode GIF pixel stream.
		datum = bits = count = first = top = pi = bi = 0;
		for (i = 0; i < npix;) {
			if (top == 0) {
				if (bits < code_size) {
					// Load bytes until there are enough bits for a code.
					if (count == 0) {
						// Read a new data block.
						count = readBlock();
						if (count <= 0) {
							break;
						}
						bi = 0;
					}
					datum += (((int) block[bi]) & 0xff) << bits;
					bits += 8;
					bi++;
					count--;
					continue;
				}
				// Get the next code.
				code = datum & code_mask;
				datum >>= code_size;
				bits -= code_size;

				// Interpret the code
				if ((code > available) || (code == end_of_information)) {
					break;
				}
				if (code == clear) {
					// Reset decoder.
					code_size = data_size + 1;
					code_mask = (1 << code_size) - 1;
					available = clear + 2;
					old_code = NullCode;
					continue;
				}
				if (old_code == NullCode) {
					pixelStack[top++] = suffix[code];
					old_code = code;
					first = code;
					continue;
				}
				in_code = code;
				if (code == available) {
					pixelStack[top++] = (byte) first;
					code = old_code;
				}
				while (code > clear) {
					pixelStack[top++] = suffix[code];
					code = prefix[code];
				}
				first = ((int) suffix[code]) & 0xff;
				// Add a new string to the string table,
				if (available >= MaxStackSize) {
					break;
				}
				pixelStack[top++] = (byte) first;
				prefix[available] = (short) old_code;
				suffix[available] = (byte) first;
				available++;
				if (((available & code_mask) == 0)
						&& (available < MaxStackSize)) {
					code_size++;
					code_mask += available;
				}
				old_code = in_code;
			}

			// Pop a pixel off the pixel stack.
			top--;
			pixels[pi++] = pixelStack[top];
			i++;
		}
		for (i = pi; i < npix; i++) {
			pixels[i] = 0; // clear missing pixels
		}
	}

	protected boolean err() {
		return status != STATUS_OK;
	}

	// to initia variable
	public void init() {
		status = STATUS_OK;
		frameCount = 0;
		frames = new Vector<GifFrame>();
		gct = null;
		lct = null;
	}

	protected int read() {
		int curByte = 0;
		try {
			curByte = in.read();
		} catch (Exception e) {
			status = STATUS_FORMAT_ERROR;
		}
		return curByte;
	}

	protected int readBlock() {
		blockSize = read();
		int n = 0;
		if (blockSize > 0) {
			try {
				int count = 0;
				while (n < blockSize) {
					count = in.read(block, n, blockSize - n);
					if (count == -1) {
						break;
					}
					n += count;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (n < blockSize) {
				status = STATUS_FORMAT_ERROR;
			}
		}
		return n;
	}

	// Global Color Table
	protected int[] readColorTable(int ncolors) {
		int nbytes = 3 * ncolors;
		int[] tab = null;
		byte[] c = new byte[nbytes];
		int n = 0;
		try {
			n = in.read(c);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (n < nbytes) {
			status = STATUS_FORMAT_ERROR;
		} else {
			tab = new int[256]; // max size to avoid bounds checks
			int i = 0;
			int j = 0;
			while (i < ncolors) {
				int r = ((int) c[j++]) & 0xff;
				int g = ((int) c[j++]) & 0xff;
				int b = ((int) c[j++]) & 0xff;
				tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
			}
		}
		return tab;
	}

	// Image Descriptor
	protected void readContents() {
		// read GIF file content blocks
		boolean done = false;
		while (!(done || err())) {
			int code = read();
			switch (code) {
			case 0x2C: // image separator
				readImage();
				break;
			case 0x21: // extension
				code = read();
				switch (code) {
				case 0xf9: // graphics control extension
					readGraphicControlExt();
					break;

				case 0xff: // application extension
					readBlock();
					String app = "";
					for (int i = 0; i < 11; i++) {
						app += (char) block[i];
					}
					if (app.equals("NETSCAPE2.0")) {
						readNetscapeExt();
					} else {
						skip(); // don't care
					}
					break;
				default: // uninteresting extension
					skip();
				}
				break;

			case 0x3b: // terminator
				done = true;
				break;

			case 0x00: // bad byte, but keep going and see what happens
				break;
			default:
				status = STATUS_FORMAT_ERROR;
			}
		}
	}

	protected void readGraphicControlExt() {
		read(); // block size
		int packed = read(); // packed fields
		dispose = (packed & 0x1c) >> 2; // disposal method
		if (dispose == 0) {
			dispose = 1; // elect to keep old image if discretionary
		}
		transparency = (packed & 1) != 0;
		delay = readShort() * 10; // delay in milliseconds
		transIndex = read(); // transparent color index
		read(); // block terminator
	}

	// to get Stream - Head
	protected void readHeader() {
		String id = "";
		for (int i = 0; i < 6; i++) {
			id += (char) read();
		}
		if (!id.startsWith("GIF")) {
			status = STATUS_FORMAT_ERROR;
			return;
		}
		readLSD();
		if (gctFlag && !err()) {
			gct = readColorTable(gctSize);
			bgColor = gct[bgIndex];
		}
	}

	protected void readImage() {
		// offset of X
		ix = readShort(); // (sub)image position & size
		// offset of Y
		iy = readShort();
		// width of bitmap
		iw = readShort();
		// height of bitmap
		ih = readShort();

		// Local Color Table Flag
		int packed = read();
		lctFlag = (packed & 0x80) != 0; // 1 - local color table flag

		// Interlace Flag, to array with interwoven if ENABLE, with order
		// otherwise
		interlace = (packed & 0x40) != 0; // 2 - interlace flag
		// 3 - sort flag
		// 4-5 - reserved
		lctSize = 2 << (packed & 7); // 6-8 - local color table size
		if (lctFlag) {
			lct = readColorTable(lctSize); // read table
			act = lct; // make local table active
		} else {
			act = gct; // make global table active
			if (bgIndex == transIndex) {
				bgColor = 0;
			}
		}
		int save = 0;
		if (transparency) {
			save = act[transIndex];
			act[transIndex] = 0; // set transparent color if specified
		}
		if (act == null) {
			status = STATUS_FORMAT_ERROR; // no color table defined
		}
		if (err()) {
			return;
		}
		decodeImageData(); // decode pixel data
		skip();
		if (err()) {
			return;
		}
		frameCount++;
		// create new image to receive frame data
		image = Bitmap.createBitmap(width, height, Config.ARGB_4444);
		// createImage(width, height);
		setPixels(); // transfer pixel data to image
		frames.addElement(new GifFrame(image, delay)); // add image to frame
		// list
		if (transparency) {
			act[transIndex] = save;
		}
		resetFrame();
	}

	// Logical Screen Descriptor
	protected void readLSD() {
		// logical screen size
		width = readShort();
		height = readShort();
		// packed fields
		int packed = read();
		gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
		// 2-4 : color resolution
		// 5 : gct sort flag
		gctSize = 2 << (packed & 7); // 6-8 : gct size
		bgIndex = read(); // background color index
		pixelAspect = read(); // pixel aspect ratio
	}

	protected void readNetscapeExt() {
		do {
			readBlock();
			if (block[0] == 1) {
				// loop count sub-block
				int b1 = ((int) block[1]) & 0xff;
				int b2 = ((int) block[2]) & 0xff;
				loopCount = (b2 << 8) | b1;
			}
		} while ((blockSize > 0) && !err());
	}

	// read 8 bit data
	protected int readShort() {
		// read 16-bit value, LSB first
		return read() | (read() << 8);
	}

	protected void resetFrame() {
		lastDispose = dispose;
		lrx = ix;
		lry = iy;
		lrw = iw;
		lrh = ih;
		lastImage = image;
		lastBgColor = bgColor;
		dispose = 0;
		transparency = false;
		delay = 0;
		lct = null;
	}

	/**
	 * Skips variable length blocks up to and including next zero length block.
	 */
	protected void skip() {
		do {
			readBlock();
		} while ((blockSize > 0) && !err());
	}
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/GlobalOnItemClickManagerUtils.java
================================================
package com.rance.chatui.util;

import android.content.Context;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;

import com.rance.chatui.adapter.EmotionGridViewAdapter;

/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 * 点击表情的全局监听管理类
 */
public class GlobalOnItemClickManagerUtils {

    private static GlobalOnItemClickManagerUtils instance;
    private EditText mEditText;//输入框
    private static Context mContext;

    public static GlobalOnItemClickManagerUtils getInstance(Context context) {
        mContext = context;
        if (instance == null) {
            synchronized (GlobalOnItemClickManagerUtils.class) {
                if (instance == null) {
                    instance = new GlobalOnItemClickManagerUtils();
                }
            }
        }
        return instance;
    }

    public void attachToEditText(EditText editText) {
        mEditText = editText;
    }

    public AdapterView.OnItemClickListener getOnItemClickListener() {
        return new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Object itemAdapter = parent.getAdapter();

                if (itemAdapter instanceof EmotionGridViewAdapter) {
                    // 点击的是表情
                    EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemAdapter;

                    if (position == emotionGvAdapter.getCount() - 1) {
                        // 如果点击了最后一个回退按钮,则调用删除键事件
                        mEditText.dispatchKeyEvent(new KeyEvent(
                                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
                    } else {
                        // 如果点击了表情,则添加到输入框中
                        String emotionName = emotionGvAdapter.getItem(position);

                        // 获取当前光标位置,在指定位置上添加表情图片文本
                        int curPosition = mEditText.getSelectionStart();
                        StringBuilder sb = new StringBuilder(mEditText.getText().toString());
                        sb.insert(curPosition, emotionName);

                        // 特殊文字处理,将表情等转换一下
                        mEditText.setText(Utils.getEmotionContent(mContext, mEditText, sb.toString()));

                        // 将光标设置到新增完表情的右侧
                        mEditText.setSelection(curPosition + emotionName.length());
                    }

                }
            }
        };
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/util/MediaManager.java
================================================
package com.rance.chatui.util;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;

/**
 * 作者:Rance on 2016/12/15 15:11
 * 邮箱:rance935@163.com
 */
public class MediaManager {

    private static MediaPlayer mMediaPlayer;
    private static boolean isPause;

    /**
     * 播放音乐
     *
     * @param filePath
     * @param onCompletionListener
     */
    public static void playSound(final String filePath, final OnCompletionListener onCompletionListener) {
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();

            //设置一个error监听器
            mMediaPlayer.setOnErrorListener(new OnErrorListener() {

                public boolean onError(MediaPlayer arg0, int arg1, int arg2) {
                    mMediaPlayer.reset();
                    return false;
                }
            });
        } else {
            mMediaPlayer.reset();
        }
        try {
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setOnCompletionListener(onCompletionListener);
            mMediaPlayer.setDataSource(filePath);
            mMediaPlayer.prepare();
            mMediaPlayer.start();
        } catch (Exception e) {

        }
    }

    /**
     * 暂停播放
     */
    public static void pause() {
        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的时候
            mMediaPlayer.pause();
            isPause = true;
        }
    }

    /**
     * 当前是isPause状态
     */
    public static void resume() {
        if (mMediaPlayer != null && isPause) {
            mMediaPlayer.start();
            isPause = false;
        }
    }

    /**
     * 释放资源
     */
    public static void release() {
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/MessageCenter.java
================================================
package com.rance.chatui.util;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import com.rance.chatui.enity.Link;
import com.rance.chatui.enity.MessageInfo;

import org.greenrobot.eventbus.EventBus;

/**
 * Created by chengz
 *
 * @date 2017/8/4.
 */

public class MessageCenter {
    private static final String TAG = "MessageCenter";
    //including images and links
    public final static String MIME_TYPE_IMAGE = "image/*";
    public final static String MIME_TYPE_IMAGE_JPG = "image/jpg";
    public final static String MIME_TYPE_IMAGE_JPEG = "image/jpeg";
    public final static String MIME_TYPE_IMAGE_PNG = "image/png";
    public final static String MIME_TYPE_IMAGE_BMP = "image/x-ms-bmp";
    public final static String MIME_TYPE_IMAGE_OTHER = "image/x-adobe-dng";
    public final static String MIME_TYPE_PDF = "application/pdf";
    public final static String MIME_TYPE_XLS = "application/vnd.ms-excel";
    public final static String MIME_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    public final static String MIME_TYPE_DOC = "application/msword";
    public final static String MIME_TYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    public final static String MIME_TYPE_PPT = "application/vnd.ms-powerpoint";
    public final static String MIME_TYPE_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    public final static String MIME_TYPE_TEXT = "text/plain";
    /**
     * process received messages
     * @param bundle
     * @param mimeType
     */
    public static void handleIncoming(Bundle bundle, String mimeType, Activity activity) {
        for (String key : bundle.keySet()) {
            Log.e(TAG, "handleIncomeAction: " + key + " => " + bundle.get(key) + ";");
        }
//
//        Log.e(TAG, "handleIncomeAction: ->" + mimeType);
        Log.e(TAG, "handleIncoming: mimeType->" + mimeType);
        if (mimeType == null) {
            return;
        }
        switch (mimeType) {
            case MIME_TYPE_IMAGE:
            case MIME_TYPE_IMAGE_JPG:
            case MIME_TYPE_IMAGE_JPEG:
            case MIME_TYPE_IMAGE_PNG:
            case MIME_TYPE_IMAGE_BMP:
            case MIME_TYPE_IMAGE_OTHER:
                if (bundle.containsKey("url") && bundle.getString("url") != null
                        && !"" .equals(bundle.getString("url"))) {
                    Log.e(TAG, "handleIncoming: url->" + bundle.getString("url") );
                    // link
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setMimeType(mimeType);
                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_LINK);
                    Link link = new Link();
                    link.setSubject(bundle.getString(Intent.EXTRA_SUBJECT));
                    link.setText(bundle.getString(Intent.EXTRA_TEXT));
                    link.setStream(bundle.get(Intent.EXTRA_STREAM).toString());
                    link.setUrl(bundle.getString("url"));
                    messageInfo.setObject(link);
                    EventBus.getDefault().post(messageInfo);
                } else {
                    // image
                    Log.e(TAG, "handleIncoming: stream ->" + bundle.getString(Intent.EXTRA_STREAM) );
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setMimeType(mimeType);
                    messageInfo.setFilepath(bundle.get(Intent.EXTRA_STREAM).toString());
                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);
                    EventBus.getDefault().post(messageInfo);
                }
                break;
            case MIME_TYPE_PDF:
            case MIME_TYPE_DOC:
            case MIME_TYPE_DOCX:
            case MIME_TYPE_XLS:
            case MIME_TYPE_XLSX:
            case MIME_TYPE_PPT:
            case MIME_TYPE_PPTX:
            case MIME_TYPE_TEXT:
                MessageInfo messageInfo = new MessageInfo();
                messageInfo.setMimeType(mimeType);
                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_FILE);
                messageInfo.setFilepath(FileUtils.getFileAbsolutePath(activity, (Uri) bundle.get(Intent.EXTRA_STREAM)));
                EventBus.getDefault().post(messageInfo);
                break;
            default:
                break;
        }
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/PhotoUtils.java
================================================
package com.rance.chatui.util;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.util.Log;

import com.rance.chatui.base.BaseFragment;

/**
 * Created by chengz
 *
 * @date 2017/8/1.
 */

public class PhotoUtils {
    private static final String TAG = "PhotoUtils";

    /**
     * 拍照方法
     * @param baseFragment
     * @param imageUri
     * @param requestCodeCamera
     */
    public static void takePicture(BaseFragment baseFragment, Uri imageUri, int requestCodeCamera) {
        Intent intentCamera = new Intent();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
            intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        Log.e(TAG, "takePicture: ->" + requestCodeCamera);
        baseFragment.startActivityForResult(intentCamera, requestCodeCamera);
    }

    /**
     * 裁剪图片
     * @param activity
     * @param orgUri
     * @param desUri
     * @param aspectX
     * @param aspectY
     * @param width
     * @param height
     * @param requestCode
     */
    public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX,
                                    int aspectY, int width, int height, int requestCode) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }

        intent.setDataAndType(orgUri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", aspectX);
        intent.putExtra("aspectY", aspectY);
        intent.putExtra("outputX", width);
        intent.putExtra("outputY", height);
        intent.putExtra("scale", true);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
        intent.putExtra("return-data", false);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("noFaceDetection", true);
        activity.startActivityForResult(intent, requestCode);
    }

    /**
     * open picture
     * @param activity
     * @param requestCode
     */
    public static void openPic(Activity activity, int requestCode) {
        Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
        photoPickerIntent.setType("image/*");
        activity.startActivityForResult(photoPickerIntent, requestCode);
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/util/PopupWindowFactory.java
================================================
package com.rance.chatui.util;

import android.content.Context;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;


/**
 * 作者:Rance on 2016/11/29 10:47
 * 邮箱:rance935@163.com
 */
public class PopupWindowFactory {

    private Context mContext;

    private PopupWindow mPop;

    /**
     * @param mContext 上下文
     * @param view PopupWindow显示的布局文件
     */
    public PopupWindowFactory(Context mContext, View view){
        this(mContext,view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    /**
     * @param mContext 上下文
     * @param view PopupWindow显示的布局文件
     * @param width PopupWindow的宽
     * @param heigth PopupWindow的高
     */
    public PopupWindowFactory(Context mContext, View view, int width, int heigth){
        init(mContext,view,width,heigth);
    }


    private void init(Context mContext, View view, int width, int heigth){
        this.mContext = mContext;

        //下面这两个必须有!!
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);

        // PopupWindow(布局,宽度,高度)
        mPop = new PopupWindow(view,width,heigth,true);
        mPop.setFocusable(true);

        // 重写onKeyListener,按返回键消失
        view.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_BACK) {
                    mPop.dismiss();
                    return true;
                }
                return false;
            }
        });

        //点击其他地方消失
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (mPop != null && mPop.isShowing()) {
                    mPop.dismiss();
                    return true;
                }
                return false;
            }});


    }

    public PopupWindow getPopupWindow(){
        return mPop;
    }


    /**
     * 以触发弹出窗的view为基准,出现在view的内部上面,弹出的pop_view左上角正对view的左上角
     * @param parent view
     * @param gravity 在view的什么位置 Gravity.CENTER、Gravity.TOP......
     * @param x 与控件的x坐标距离
     * @param y 与控件的y坐标距离
     */
    public void showAtLocation(View parent, int gravity, int x, int y){

        if(mPop.isShowing()){
            return ;
        }
        mPop.showAtLocation(parent, gravity, x, y);

    }

    /**
     * 以触发弹出窗的view为基准,出现在view的正下方,弹出的pop_view左上角正对view的左下角
     * @param anchor view
     */
    public void showAsDropDown(View anchor){
        showAsDropDown(anchor,0,0);
    }

    /**
     * 以触发弹出窗的view为基准,出现在view的正下方,弹出的pop_view左上角正对view的左下角
     * @param anchor view
     * @param xoff 与view的x坐标距离
     * @param yoff 与view的y坐标距离
     */
    public void showAsDropDown(View anchor, int xoff, int yoff){
        if(mPop.isShowing()){
            return ;
        }

        mPop.showAsDropDown(anchor, xoff, yoff);
    }

    /**
     * 隐藏PopupWindow
     */
    public void dismiss(){
        if (mPop.isShowing()) {
            mPop.dismiss();
        }
    }

}


================================================
FILE: app/src/main/java/com/rance/chatui/util/Utils.java
================================================
package com.rance.chatui.util;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 作者:Rance on 2016/12/20 16:41
 * 邮箱:rance935@163.com
 */
public class Utils {
    /**
     * dp转dip
     *
     * @param context
     * @param dp
     * @return
     */
    public static int dp2px(Context context, float dp) {
        float density = context.getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5F);
    }

    /**
     * 文本中的emojb字符处理为表情图片
     *
     * @param context
     * @param tv
     * @param source
     * @return
     */
    public static SpannableString getEmotionContent(final Context context, final TextView tv, String source) {
        SpannableString spannableString = new SpannableString(source);
        Resources res = context.getResources();

        String regexEmotion = "\\[([\u4e00-\u9fa5\\w])+\\]";
        Pattern patternEmotion = Pattern.compile(regexEmotion);
        Matcher matcherEmotion = patternEmotion.matcher(spannableString);

        while (matcherEmotion.find()) {
            // 获取匹配到的具体字符
            String key = matcherEmotion.group();
            // 匹配字符串的开始位置
            int start = matcherEmotion.start();
            // 利用表情名字获取到对应的图片
            Integer imgRes = EmotionUtils.EMOTION_STATIC_MAP.get(key);
            if (imgRes != null) {
                // 压缩表情图片
                int size = (int) tv.getTextSize() * 13 / 8;
                Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);
                Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);

                ImageSpan span = new ImageSpan(context, scaleBitmap);
                spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
        return spannableString;
    }

    /**
     * 返回当前时间的格式为 yyyyMMddHHmmss
     *
     * @return
     */
    public static String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(System.currentTimeMillis());
    }

    //毫秒转秒
    public static String long2String(long time) {
        //毫秒转秒
        int sec = (int) time / 1000;
        int min = sec / 60;    //分钟
        sec = sec % 60;        //秒
        if (min < 10) {    //分钟补0
            if (sec < 10) {    //秒补0
                return "0" + min + ":0" + sec;
            } else {
                return "0" + min + ":" + sec;
            }
        } else {
            if (sec < 10) {    //秒补0
                return min + ":0" + sec;
            } else {
                return min + ":" + sec;
            }
        }
    }

    /**
     * 毫秒转化时分秒毫秒
     *
     * @param ms
     * @return
     */
    public static String formatTime(Long ms) {
        Integer ss = 1000;
        Integer mi = ss * 60;
        Integer hh = mi * 60;
        Integer dd = hh * 24;

        Long day = ms / dd;
        Long hour = (ms - day * dd) / hh;
        Long minute = (ms - day * dd - hour * hh) / mi;
        Long second = (ms - day * dd - hour * hh - minute * mi) / ss;

        StringBuffer sb = new StringBuffer();
        if (day > 0) {
            sb.append(day + "d");
        }
        if (hour > 0) {
            sb.append(hour + "h");
        }
        if (minute > 0) {
            sb.append(minute + "′");
        }
        if (second > 0) {
            sb.append(second + "″");
        }
        return sb.toString();
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/widget/BubbleDrawable.java
================================================
package com.rance.chatui.widget;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;

/**
 * 作者:Rance on 2016/12/15 11:28
 * 邮箱:rance935@163.com
 * 自定义聊天气泡drawable
 */
public class BubbleDrawable extends Drawable {
    private RectF mRect;
    private Path mPath = new Path();
    private BitmapShader mBitmapShader;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private float mArrowWidth;
    private float mAngle;
    private float mArrowHeight;
    private float mArrowPosition;
    private int bubbleColor;
    private Bitmap bubbleBitmap;
    private ArrowLocation mArrowLocation;
    private BubbleType bubbleType;
    private BubbleDrawable(Builder builder) {
        this.mRect = builder.mRect;
        this.mAngle = builder.mAngle;
        this.mArrowHeight = builder.mArrowHeight;
        this.mArrowWidth = builder.mArrowWidth;
        this.mArrowPosition = builder.mArrowPosition;
        this.bubbleColor = builder.bubbleColor;
        this.bubbleBitmap = builder.bubbleBitmap;
        this.mArrowLocation = builder.mArrowLocation;
        this.bubbleType = builder.bubbleType;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
    }

    @Override
    public void draw(Canvas canvas) {
        setUp(canvas);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    private void setUpPath(ArrowLocation mArrowLocation, Path path){
        switch (mArrowLocation){
            case LEFT:
                setUpLeftPath(mRect, path);
                break;
            case RIGHT:
                setUpRightPath(mRect, path);
                break;
            case TOP:
                setUpTopPath(mRect, path);
                break;
            case BOTTOM:
                setUpBottomPath(mRect, path);
                break;
        }
    }

    private void setUp(Canvas canvas){
        switch (bubbleType){
            case COLOR:
                mPaint.setColor(bubbleColor);
                break;
            case BITMAP:
                if (bubbleBitmap == null)
                    return;
                if (mBitmapShader == null){
                    mBitmapShader = new BitmapShader(bubbleBitmap,
                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
                }
                mPaint.setShader(mBitmapShader);
                setUpShaderMatrix();
                break;
        }
        setUpPath(mArrowLocation, mPath);
        canvas.drawPath(mPath, mPaint);
    }

    private void setUpLeftPath(RectF rect, Path path){

        path.moveTo(mArrowWidth + rect.left + mAngle, rect.top);
        path.lineTo(rect.width() - mAngle, rect.top);
        path.arcTo(new RectF(rect.right - mAngle , rect.top, rect.right,
                mAngle + rect.top), 270, 90);
        path.lineTo(rect.right, rect.bottom - mAngle);
        path.arcTo(new RectF(rect.right - mAngle , rect.bottom - mAngle,
                rect.right, rect.bottom), 0, 90);
        path.lineTo(rect.left + mArrowWidth + mAngle, rect.bottom);
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mAngle ,
                mAngle + rect.left + mArrowWidth, rect.bottom), 90, 90);
        path.lineTo(rect.left + mArrowWidth,  mArrowHeight + mArrowPosition);
        path.lineTo(rect.left, mArrowPosition + mArrowHeight / 2);
        path.lineTo(rect.left + mArrowWidth, mArrowPosition);
        path.lineTo(rect.left + mArrowWidth, rect.top + mAngle);
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.top, mAngle
                + rect.left + mArrowWidth, mAngle + rect.top), 180, 90);
        path.close();
    }

    private void setUpTopPath(RectF rect, Path path){
        path.moveTo(rect.left + Math.min(mArrowPosition, mAngle), rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowPosition,  rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowWidth / 2 + mArrowPosition, rect.top);
        path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.top + mArrowHeight);
        path.lineTo(rect.right - mAngle, rect.top + mArrowHeight);

        path.arcTo(new RectF(rect.right - mAngle,
                rect.top + mArrowHeight , rect.right, mAngle + rect.top + mArrowHeight), 270, 90);
        path.lineTo(rect.right, rect.bottom - mAngle);

        path.arcTo(new RectF(rect.right - mAngle , rect.bottom - mAngle,
                rect.right , rect.bottom), 0, 90);
        path.lineTo(rect.left + mAngle, rect.bottom);

        path.arcTo(new RectF(rect.left, rect.bottom - mAngle,
                mAngle + rect.left , rect.bottom), 90, 90);
        path.lineTo(rect.left , rect.top + mArrowHeight + mAngle);
        path.arcTo(new RectF(rect.left, rect.top + mArrowHeight , mAngle
                + rect.left, mAngle + rect.top + mArrowHeight), 180, 90);
        path.close();
    }

    private void setUpRightPath(RectF rect, Path path){

        path.moveTo(rect.left + mAngle, rect.top);
        path.lineTo(rect.width() - mAngle - mArrowWidth, rect.top);
        path.arcTo(new RectF(rect.right - mAngle - mArrowWidth,
                rect.top, rect.right - mArrowWidth, mAngle + rect.top), 270, 90);
        path.lineTo(rect.right - mArrowWidth,  mArrowPosition);
        path.lineTo(rect.right, mArrowPosition + mArrowHeight / 2);
        path.lineTo(rect.right - mArrowWidth, mArrowPosition + mArrowHeight);
        path.lineTo(rect.right - mArrowWidth, rect.bottom - mAngle);

        path.arcTo(new RectF(rect.right - mAngle - mArrowWidth, rect.bottom - mAngle,
                rect.right - mArrowWidth, rect.bottom), 0, 90);
        path.lineTo(rect.left + mArrowWidth, rect.bottom);

        path.arcTo(new RectF(rect.left, rect.bottom - mAngle,
                mAngle + rect.left , rect.bottom), 90, 90);

        path.arcTo(new RectF(rect.left, rect.top, mAngle
                + rect.left, mAngle + rect.top), 180, 90);
        path.close();
    }

    private void setUpBottomPath(RectF rect, Path path){

        path.moveTo(rect.left + mAngle, rect.top);
        path.lineTo(rect.width() - mAngle, rect.top);
        path.arcTo(new RectF(rect.right - mAngle,
                rect.top, rect.right, mAngle + rect.top), 270, 90);

        path.lineTo(rect.right, rect.bottom - mArrowHeight - mAngle);
        path.arcTo(new RectF(rect.right - mAngle, rect.bottom - mAngle - mArrowHeight,
                rect.right, rect.bottom - mArrowHeight), 0, 90);

        path.lineTo(rect.left + mArrowWidth + mArrowPosition,  rect.bottom - mArrowHeight);
        path.lineTo(rect.left + mArrowPosition + mArrowWidth / 2, rect.bottom);
        path.lineTo(rect.left + mArrowPosition, rect.bottom - mArrowHeight);
        path.lineTo(rect.left + Math.min(mAngle, mArrowPosition), rect.bottom - mArrowHeight);

        path.arcTo(new RectF(rect.left, rect.bottom - mAngle - mArrowHeight,
                mAngle + rect.left , rect.bottom - mArrowHeight), 90, 90);
        path.lineTo(rect.left, rect.top + mAngle);
        path.arcTo(new RectF(rect.left, rect.top, mAngle
                + rect.left, mAngle + rect.top), 180, 90);
        path.close();
    }

    private void setUpShaderMatrix() {
        float scale;
        Matrix mShaderMatrix = new Matrix();
        mShaderMatrix.set(null);
        int mBitmapWidth = bubbleBitmap.getWidth();
        int mBitmapHeight = bubbleBitmap.getHeight();
        float scaleX = getIntrinsicWidth() / (float) mBitmapWidth;
        float scaleY = getIntrinsicHeight() / (float) mBitmapHeight;
        scale = Math.min(scaleX, scaleY);
        mShaderMatrix.postScale(scale, scale);
        mShaderMatrix.postTranslate(mRect.left, mRect.top);
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

    @Override
    public int getIntrinsicWidth() {
        return (int)mRect.width();
    }

    @Override
    public int getIntrinsicHeight() {
        return (int)mRect.height();
    }

    public static class Builder{
        public static float DEFAULT_ARROW_WITH = 25;
        public static float DEFAULT_ARROW_HEIGHT = 25;
        public static float DEFAULT_ANGLE = 20;
        public static float DEFAULT_ARROW_POSITION = 50;
        public static int DEFAULT_BUBBLE_COLOR = Color.RED;
        private RectF mRect;
        private float mArrowWidth = DEFAULT_ARROW_WITH;
        private float mAngle = DEFAULT_ANGLE;
        private float mArrowHeight = DEFAULT_ARROW_HEIGHT;
        private float mArrowPosition = DEFAULT_ARROW_POSITION;
        private int bubbleColor = DEFAULT_BUBBLE_COLOR;
        private Bitmap bubbleBitmap;
        private BubbleType bubbleType = BubbleType.COLOR;
        private ArrowLocation mArrowLocation = ArrowLocation.LEFT;

        public Builder rect(RectF rect){
            this.mRect = rect;
            return this;
        }

        public Builder arrowWidth(float mArrowWidth){
            this.mArrowWidth = mArrowWidth;
            return this;
        }

        public Builder angle(float mAngle){
            this.mAngle = mAngle * 2;
            return this;
        }

        public Builder arrowHeight(float mArrowHeight){
            this.mArrowHeight = mArrowHeight;
            return this;
        }

        public Builder arrowPosition(float mArrowPosition){
            this.mArrowPosition = mArrowPosition;
            return this;
        }

        public Builder bubbleColor(int bubbleColor){
            this.bubbleColor = bubbleColor;
            bubbleType(BubbleType.COLOR);
            return this;
        }

        public Builder bubbleBitmap(Bitmap bubbleBitmap){
            this.bubbleBitmap = bubbleBitmap;
            bubbleType(BubbleType.BITMAP);
            return this;
        }

        public Builder arrowLocation(ArrowLocation arrowLocation){
            this.mArrowLocation = arrowLocation;
            return this;
        }

        public Builder bubbleType(BubbleType bubbleType){
            this.bubbleType = bubbleType;
            return this;
        }

        public BubbleDrawable build(){
            if (mRect == null){
                throw new IllegalArgumentException("BubbleDrawable Rect can not be null");
            }
            return new BubbleDrawable(this);
        }
    }

    public enum ArrowLocation{
        LEFT(0x00),
        RIGHT(0x01),
        TOP(0x02),
        BOTTOM(0x03);

        private int mValue;

        ArrowLocation(int value){
            this.mValue = value;
        }

        public static ArrowLocation mapIntToValue(int stateInt) {
            for (ArrowLocation value : ArrowLocation.values()) {
                if (stateInt == value.getIntValue()) {
                    return value;
                }
            }
            return getDefault();
        }

        public static ArrowLocation getDefault(){
            return LEFT;
        }

        public int getIntValue() {
            return mValue;
        }
    }

    public enum BubbleType{
        COLOR,
        BITMAP
    }
}


================================================
FILE: app/src/main/java/com/rance/chatui/widget/BubbleImageView.java
================================================
package com.rance.chatui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.ImageView;

import com.rance.chatui.R;

/**
 * 作者:Rance on 2016/12/15 10:49
 * 邮箱:rance935@163.com
 * 自定义聊天气泡图片
 */
public class BubbleImageView extends ImageView {
    private BubbleDrawable bubbleDrawable;
    private Drawable sourceDrawable;
    private float mArrowWidth;
    private float mAngle;
    private float mArrowHeight;
    private float mArrowPosition;
    private Bitmap mBitmap;
    private BubbleDrawable.ArrowLocation mArrowLocation;
    public BubbleImageView(Context context) {
        super(context);
        initView(null);
    }

    public BubbleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(attrs);
    }

    public BubbleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(attrs);
    }

    private void initView(AttributeSet attrs){
        if (attrs != null){
            TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.BubbleView);
            mArrowWidth = array.getDimension(R.styleable.BubbleView_arrowWidth,
                    BubbleDrawable.Builder.DEFAULT_ARROW_WITH);
            mArrowHeight = array.getDimension(R.styleable.BubbleView_arrowHeight,
                    BubbleDrawable.Builder.DEFAULT_ARROW_HEIGHT);
            mAngle = array.getDimension(R.styleable.BubbleView_angle,
                    BubbleDrawable.Builder.DEFAULT_ANGLE);
            mArrowPosition = array.getDimension(R.styleable.BubbleView_arrowPosition,
                    BubbleDrawable.Builder.DEFAULT_ARROW_POSITION);
            int location = array.getInt(R.styleable.BubbleView_arrowLocation, 0);
            mArrowLocation = BubbleDrawable.ArrowLocation.mapIntToValue(location);
            array.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        if (width <= 0 && height > 0){
            setMeasuredDimension(height , height);
        }
        if (height <= 0 && width > 0){
            setMeasuredDimension(width , width);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w > 0 && h > 0){
            setUp(w, h);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        setUp();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int saveCount = canvas.getSaveCount();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        if (bubbleDrawable != null)
            bubbleDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }

    private void setUp(int left, int right, int top, int bottom){
        Log.d("setUp", "left-->" + left);
        Log.d("setUp", "right-->" + right);
        Log.d("setUp
Download .txt
gitextract_em0rdfsy/

├── .gitignore
├── .idea/
│   ├── .name
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── encodings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── libs/
│   │   └── BaiduLBS_Android.jar
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── rance/
│       │               └── chatui/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── rance/
│       │   │           └── chatui/
│       │   │               ├── adapter/
│       │   │               │   ├── ChatAdapter.java
│       │   │               │   ├── CommonFragmentPagerAdapter.java
│       │   │               │   ├── ContactAdapter.java
│       │   │               │   ├── EmotionGridViewAdapter.java
│       │   │               │   ├── EmotionPagerAdapter.java
│       │   │               │   └── holder/
│       │   │               │       ├── BaseViewHolder.java
│       │   │               │       ├── ChatAcceptViewHolder.java
│       │   │               │       └── ChatSendViewHolder.java
│       │   │               ├── base/
│       │   │               │   ├── BaseFragment.java
│       │   │               │   └── MyApplication.java
│       │   │               ├── enity/
│       │   │               │   ├── FullImageInfo.java
│       │   │               │   ├── IMContact.java
│       │   │               │   ├── Link.java
│       │   │               │   └── MessageInfo.java
│       │   │               ├── ui/
│       │   │               │   ├── activity/
│       │   │               │   │   ├── ContactActivity.java
│       │   │               │   │   ├── FullImageActivity.java
│       │   │               │   │   └── IMActivity.java
│       │   │               │   └── fragment/
│       │   │               │       ├── ChatEmotionFragment.java
│       │   │               │       └── ChatFunctionFragment.java
│       │   │               ├── util/
│       │   │               │   ├── AudioRecorderUtils.java
│       │   │               │   ├── CheckPermissionUtils.java
│       │   │               │   ├── Constants.java
│       │   │               │   ├── EmotionUtils.java
│       │   │               │   ├── FileUtils.java
│       │   │               │   ├── GifOpenHelper.java
│       │   │               │   ├── GlobalOnItemClickManagerUtils.java
│       │   │               │   ├── MediaManager.java
│       │   │               │   ├── MessageCenter.java
│       │   │               │   ├── PhotoUtils.java
│       │   │               │   ├── PopupWindowFactory.java
│       │   │               │   └── Utils.java
│       │   │               └── widget/
│       │   │                   ├── BubbleDrawable.java
│       │   │                   ├── BubbleImageView.java
│       │   │                   ├── BubbleLinearLayout.java
│       │   │                   ├── ChatContextMenu.java
│       │   │                   ├── EmotionInputDetector.java
│       │   │                   ├── GifTextView.java
│       │   │                   ├── IndicatorView.java
│       │   │                   ├── NoScrollViewPager.java
│       │   │                   └── StateButton.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── bg_circle_gary.xml
│       │       │   ├── bg_circle_white.xml
│       │       │   ├── bg_surname.xml
│       │       │   ├── corners_edit.xml
│       │       │   ├── corners_edit_white.xml
│       │       │   ├── divider.xml
│       │       │   ├── record_microphone.xml
│       │       │   ├── record_microphone_bj.xml
│       │       │   ├── voice_left.xml
│       │       │   └── voice_right.xml
│       │       ├── layout/
│       │       │   ├── activity_contact.xml
│       │       │   ├── activity_full_image.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── dialog_contact.xml
│       │       │   ├── fragment_chat_emotion.xml
│       │       │   ├── fragment_chat_function.xml
│       │       │   ├── include_reply_layout.xml
│       │       │   ├── item_chat_accept.xml
│       │       │   ├── item_chat_send.xml
│       │       │   ├── item_contact.xml
│       │       │   ├── layout_microphone.xml
│       │       │   └── popup_context_menu.xml
│       │       ├── values/
│       │       │   ├── attr.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       ├── values-w820dp/
│       │       │   └── dimens.xml
│       │       └── xml/
│       │           └── file_paths.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── rance/
│                       └── chatui/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (418 symbols across 42 files)

FILE: app/src/androidTest/java/com/rance/chatui/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: app/src/main/java/com/rance/chatui/adapter/ChatAdapter.java
  class ChatAdapter (line 22) | public class ChatAdapter extends RecyclerView.Adapter<BaseViewHolder> {
    method ChatAdapter (line 28) | public ChatAdapter(List<MessageInfo> messageInfoList) {
    method addItemClickListener (line 52) | public void addItemClickListener(onItemClickListener onItemClickListen...
    method onCreateViewHolder (line 56) | @Override
    method onBindViewHolder (line 70) | @Override
    method getItemViewType (line 76) | @Override
    method getItemCount (line 81) | @Override
    method addAll (line 90) | public void addAll(List<MessageInfo> messageInfos) {
    method add (line 100) | public void add(MessageInfo messageInfo) {
    type onItemClickListener (line 110) | public interface onItemClickListener {
      method onHeaderClick (line 111) | void onHeaderClick(int position);
      method onImageClick (line 113) | void onImageClick(View view, int position);
      method onVoiceClick (line 115) | void onVoiceClick(ImageView imageView, int position);
      method onFileClick (line 117) | void onFileClick(View view, int position);
      method onLinkClick (line 119) | void onLinkClick(View view, int position);
      method onLongClickImage (line 121) | void onLongClickImage(View view, int position);
      method onLongClickText (line 123) | void onLongClickText(View view, int position);
      method onLongClickItem (line 125) | void onLongClickItem(View view, int position);
      method onLongClickFile (line 127) | void onLongClickFile(View view, int position);
      method onLongClickLink (line 129) | void onLongClickLink(View view, int position);

FILE: app/src/main/java/com/rance/chatui/adapter/CommonFragmentPagerAdapter.java
  class CommonFragmentPagerAdapter (line 13) | public class CommonFragmentPagerAdapter extends FragmentPagerAdapter {
    method CommonFragmentPagerAdapter (line 16) | public CommonFragmentPagerAdapter(FragmentManager fm, ArrayList<Fragme...
    method getItem (line 21) | @Override
    method getCount (line 26) | @Override

FILE: app/src/main/java/com/rance/chatui/adapter/ContactAdapter.java
  class ContactAdapter (line 21) | public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter....
    method ContactAdapter (line 26) | public ContactAdapter(List<IMContact> imContactList) {
    method onCreateViewHolder (line 29) | @Override
    method onBindViewHolder (line 37) | @Override
    method getItemCount (line 46) | @Override
    method onClick (line 51) | @Override
    class ViewHolder (line 60) | public class ViewHolder extends RecyclerView.ViewHolder {
      method ViewHolder (line 63) | public ViewHolder(View itemView) {
    type OnContactClickListener (line 70) | public interface OnContactClickListener {
      method onContactClick (line 71) | void onContactClick(View view, IMContact imContact);
    method setOnContactClickListener (line 74) | public void setOnContactClickListener(OnContactClickListener onContact...

FILE: app/src/main/java/com/rance/chatui/adapter/EmotionGridViewAdapter.java
  class EmotionGridViewAdapter (line 18) | public class EmotionGridViewAdapter extends BaseAdapter {
    method EmotionGridViewAdapter (line 24) | public EmotionGridViewAdapter(Context context, List<String> emotionNam...
    method getCount (line 30) | @Override
    method getItem (line 36) | @Override
    method getItemId (line 41) | @Override
    method getView (line 46) | @Override

FILE: app/src/main/java/com/rance/chatui/adapter/EmotionPagerAdapter.java
  class EmotionPagerAdapter (line 15) | public class EmotionPagerAdapter extends PagerAdapter {
    method EmotionPagerAdapter (line 19) | public EmotionPagerAdapter(List<GridView> gvs) {
    method getCount (line 23) | @Override
    method isViewFromObject (line 28) | @Override
    method destroyItem (line 33) | @Override
    method instantiateItem (line 38) | @Override

FILE: app/src/main/java/com/rance/chatui/adapter/holder/BaseViewHolder.java
  class BaseViewHolder (line 12) | public class BaseViewHolder<M> extends RecyclerView.ViewHolder {
    method BaseViewHolder (line 14) | public BaseViewHolder(View itemView) {
    method setData (line 18) | public void setData(M data) {

FILE: app/src/main/java/com/rance/chatui/adapter/holder/ChatAcceptViewHolder.java
  class ChatAcceptViewHolder (line 31) | public class ChatAcceptViewHolder extends BaseViewHolder<MessageInfo> {
    method ChatAcceptViewHolder (line 59) | public ChatAcceptViewHolder(ViewGroup parent, ChatAdapter.onItemClickL...
    method findViewByIds (line 70) | private void findViewByIds(View itemView) {
    method setData (line 91) | @Override
    method setItemLongClick (line 231) | public void setItemLongClick() {
    method setItemClick (line 262) | public void setItemClick() {

FILE: app/src/main/java/com/rance/chatui/adapter/holder/ChatSendViewHolder.java
  class ChatSendViewHolder (line 33) | public class ChatSendViewHolder extends BaseViewHolder<MessageInfo> {
    method ChatSendViewHolder (line 63) | public ChatSendViewHolder(ViewGroup parent, ChatAdapter.onItemClickLis...
    method findViewByIds (line 74) | private void findViewByIds(View itemView) {
    method setData (line 98) | @Override
    method setItemLongClick (line 247) | public void setItemLongClick() {
    method setItemClick (line 278) | public void setItemClick() {

FILE: app/src/main/java/com/rance/chatui/base/BaseFragment.java
  class BaseFragment (line 18) | public class BaseFragment extends Fragment {
    method onCreateView (line 21) | @Nullable
    method onViewCreated (line 28) | @Override
    method toastShow (line 34) | public void toastShow(int resId) {
    method toastShow (line 38) | public void toastShow(String resId) {
    method showProgressDialog (line 44) | public ProgressDialog showProgressDialog() {
    method showProgressDialog (line 51) | public ProgressDialog showProgressDialog(CharSequence message) {
    method dismissProgressDialog (line 58) | public void dismissProgressDialog() {

FILE: app/src/main/java/com/rance/chatui/base/MyApplication.java
  class MyApplication (line 11) | public class MyApplication extends Application {
    method onCreate (line 27) | @Override
    method getInstance (line 35) | public static Context getInstance() {
    method initScreenSize (line 42) | private void initScreenSize() {

FILE: app/src/main/java/com/rance/chatui/enity/FullImageInfo.java
  class FullImageInfo (line 7) | public class FullImageInfo {
    method getLocationX (line 14) | public int getLocationX() {
    method setLocationX (line 18) | public void setLocationX(int locationX) {
    method getLocationY (line 22) | public int getLocationY() {
    method setLocationY (line 26) | public void setLocationY(int locationY) {
    method getWidth (line 30) | public int getWidth() {
    method setWidth (line 34) | public void setWidth(int width) {
    method getHeight (line 38) | public int getHeight() {
    method setHeight (line 42) | public void setHeight(int height) {
    method getImageUrl (line 46) | public String getImageUrl() {
    method setImageUrl (line 50) | public void setImageUrl(String imageUrl) {

FILE: app/src/main/java/com/rance/chatui/enity/IMContact.java
  class IMContact (line 9) | public class IMContact {
    method IMContact (line 12) | public IMContact(String name, String phonenumber) {
    method getName (line 17) | public String getName() {
    method setName (line 21) | public void setName(String name) {
    method getPhonenumber (line 25) | public String getPhonenumber() {
    method setPhonenumber (line 29) | public void setPhonenumber(String phonenumber) {
    method getSurname (line 33) | public String getSurname() {
    method setSurname (line 37) | public void setSurname(String surname) {

FILE: app/src/main/java/com/rance/chatui/enity/Link.java
  class Link (line 9) | public class Link {
    method getSubject (line 12) | public String getSubject() {
    method setSubject (line 16) | public void setSubject(String subject) {
    method getText (line 20) | public String getText() {
    method setText (line 24) | public void setText(String text) {
    method getStream (line 28) | public String getStream() {
    method setStream (line 32) | public void setStream(String stream) {
    method getUrl (line 36) | public String getUrl() {
    method setUrl (line 40) | public void setUrl(String url) {

FILE: app/src/main/java/com/rance/chatui/enity/MessageInfo.java
  class MessageInfo (line 7) | public class MessageInfo {
    method getObject (line 20) | public Object getObject() {
    method setObject (line 24) | public void setObject(Object object) {
    method getFileType (line 28) | public String getFileType() {
    method setFileType (line 32) | public void setFileType(String fileType) {
    method getType (line 36) | public int getType() {
    method setType (line 40) | public void setType(int type) {
    method getContent (line 44) | public String getContent() {
    method setContent (line 48) | public void setContent(String content) {
    method getFilepath (line 52) | public String getFilepath() {
    method setFilepath (line 56) | public void setFilepath(String filepath) {
    method getSendState (line 60) | public int getSendState() {
    method setSendState (line 64) | public void setSendState(int sendState) {
    method getTime (line 68) | public String getTime() {
    method setTime (line 72) | public void setTime(String time) {
    method getHeader (line 76) | public String getHeader() {
    method setHeader (line 80) | public void setHeader(String header) {
    method getVoiceTime (line 84) | public long getVoiceTime() {
    method setVoiceTime (line 88) | public void setVoiceTime(long voiceTime) {
    method getMsgId (line 92) | public String getMsgId() {
    method setMsgId (line 96) | public void setMsgId(String msgId) {
    method getMimeType (line 100) | public String getMimeType() {
    method setMimeType (line 104) | public void setMimeType(String mimeType) {
    method toString (line 108) | @Override

FILE: app/src/main/java/com/rance/chatui/ui/activity/ContactActivity.java
  class ContactActivity (line 34) | public class ContactActivity extends AppCompatActivity implements Contac...
    method onCreate (line 40) | @Override
    method setup (line 49) | private void setup() {
    method checkPermission (line 53) | private void checkPermission() {
    method onRequestPermissionsResult (line 63) | @Override
    method readContacts (line 77) | private void readContacts() {
    method loadData (line 105) | private void loadData() {
    method onContactClick (line 111) | @Override

FILE: app/src/main/java/com/rance/chatui/ui/activity/FullImageActivity.java
  class FullImageActivity (line 32) | public class FullImageActivity extends Activity {
    method onCreate (line 42) | @Override
    method onDataSynEvent (line 60) | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    method activityEnterAnim (line 86) | private void activityEnterAnim() {
    method activityExitAnim (line 101) | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    method onBackPressed (line 114) | @Override
    method onImageClick (line 125) | public void onImageClick() {
    method onDestroy (line 135) | @Override

FILE: app/src/main/java/com/rance/chatui/ui/activity/IMActivity.java
  class IMActivity (line 53) | public class IMActivity extends AppCompatActivity {
    method onCreate (line 80) | @Override
    method findViewByIds (line 90) | private void findViewByIds() {
    method handleIncomeAction (line 102) | private void handleIncomeAction() {
    method initWidget (line 111) | private void initWidget() {
    method onHeaderClick (line 172) | @Override
    method onImageClick (line 177) | @Override
    method onVoiceClick (line 192) | @Override
    method onFileClick (line 220) | @Override
    method onLinkClick (line 236) | @Override
    method onLongClickImage (line 245) | @Override
    method onLongClickText (line 255) | @Override
    method onLongClickItem (line 262) | @Override
    method onLongClickFile (line 269) | @Override
    method onLongClickLink (line 276) | @Override
    method LoadData (line 287) | private void LoadData() {
    method MessageEventBus (line 324) | @Subscribe(threadMode = ThreadMode.MAIN)
    method onBackPressed (line 353) | @Override
    method onDestroy (line 360) | @Override

FILE: app/src/main/java/com/rance/chatui/ui/fragment/ChatEmotionFragment.java
  class ChatEmotionFragment (line 30) | public class ChatEmotionFragment extends BaseFragment {
    method onCreateView (line 36) | @Nullable
    method initWidget (line 48) | private void initWidget() {
    method initEmotion (line 79) | private void initEmotion() {
    method createEmotionGridView (line 123) | private GridView createEmotionGridView(List<String> emotionNames, int ...

FILE: app/src/main/java/com/rance/chatui/ui/fragment/ChatFunctionFragment.java
  class ChatFunctionFragment (line 41) | public class ChatFunctionFragment extends BaseFragment {
    method onCreateView (line 62) | @Nullable
    method findViewByIds (line 73) | private void findViewByIds(View rootView) {
    method autoObtainCameraPermission (line 82) | private void autoObtainCameraPermission() {
    method setItemClick (line 99) | public void setItemClick() {
    method showContact (line 159) | private void showContact() {
    method chooseFile (line 164) | private void chooseFile() {
    method takePhoto (line 174) | private void takePhoto() {
    method choosePhoto (line 185) | private void choosePhoto() {
    method onActivityResult (line 195) | public void onActivityResult(int req, int res, Intent data) {
    method onRequestPermissionsResult (line 261) | @Override
    method getImageRealPathFromURI (line 291) | public String getImageRealPathFromURI(Uri contentUri) {

FILE: app/src/main/java/com/rance/chatui/util/AudioRecorderUtils.java
  class AudioRecorderUtils (line 16) | public class AudioRecorderUtils {
    method AudioRecorderUtils (line 32) | public AudioRecorderUtils() {
    method AudioRecorderUtils (line 38) | public AudioRecorderUtils(String filePath) {
    method startRecord (line 56) | public void startRecord(Context context) {
    method stopRecord (line 100) | public long stopRecord() {
    method cancelRecord (line 128) | public void cancelRecord() {
    method run (line 144) | public void run() {
    method setOnAudioStatusUpdateListener (line 153) | public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener...
    method updateMicStatus (line 160) | private void updateMicStatus() {
    type OnAudioStatusUpdateListener (line 175) | public interface OnAudioStatusUpdateListener {
      method onUpdate (line 182) | public void onUpdate(double db, long time);
      method onStop (line 190) | public void onStop(long time, String filePath);
      method onError (line 195) | public void onError();

FILE: app/src/main/java/com/rance/chatui/util/CheckPermissionUtils.java
  class CheckPermissionUtils (line 16) | public class CheckPermissionUtils {
    method isHasPermission (line 31) | public static boolean isHasPermission(final Context context){

FILE: app/src/main/java/com/rance/chatui/util/Constants.java
  class Constants (line 7) | public class Constants {

FILE: app/src/main/java/com/rance/chatui/util/EmotionUtils.java
  class EmotionUtils (line 13) | public class EmotionUtils {

FILE: app/src/main/java/com/rance/chatui/util/FileUtils.java
  class FileUtils (line 24) | public class FileUtils {
    method getFileAbsolutePath (line 31) | @TargetApi(19)
    method getDataColumn (line 77) | public static String getDataColumn(Context context, Uri uri, String se...
    method isExternalStorageDocument (line 98) | public static boolean isExternalStorageDocument(Uri uri) {
    method isDownloadsDocument (line 107) | public static boolean isDownloadsDocument(Uri uri) {
    method isMediaDocument (line 116) | public static boolean isMediaDocument(Uri uri) {
    method isGooglePhotosUri (line 125) | public static boolean isGooglePhotosUri(Uri uri) {
    method getFileSize (line 129) | public static String getFileSize(String filePath) throws Exception {
    method getFileSize (line 134) | public static String getFileSize(File file) throws Exception {
    method getExtensionName (line 147) | public static String getExtensionName(String filePath) throws Exception {
    method getExtensionName (line 152) | public static String getExtensionName(File file) {
    method getFileName (line 157) | public static String getFileName(String filePath) {
    method formatFileSize (line 162) | private static String formatFileSize(long fileS)

FILE: app/src/main/java/com/rance/chatui/util/GifOpenHelper.java
  class GifOpenHelper (line 10) | public class GifOpenHelper {
    class GifFrame (line 13) | class GifFrame {
      method GifFrame (line 18) | public GifFrame(Bitmap im, int del) {
    method getFrameindex (line 59) | public int getFrameindex() {
    method setFrameindex (line 63) | public void setFrameindex(int frameindex) {
    method getWidth (line 94) | public int getWidth() {
    method getHeigh (line 98) | public int getHeigh() {
    method getDelay (line 109) | public int getDelay(int n) {
    method getFrameCount (line 117) | public int getFrameCount() {
    method getImage (line 121) | public Bitmap getImage() {
    method getLoopCount (line 125) | public int getLoopCount() {
    method setPixels (line 129) | protected void setPixels() {
    method getFrame (line 210) | public Bitmap getFrame(int n) {
    method nextBitmap (line 218) | public Bitmap nextBitmap() {
    method nextDelay (line 226) | public int nextDelay() {
    method read (line 231) | public int read(InputStream is) {
    method decodeImageData (line 254) | protected void decodeImageData() {
    method err (line 363) | protected boolean err() {
    method init (line 368) | public void init() {
    method read (line 376) | protected int read() {
    method readBlock (line 386) | protected int readBlock() {
    method readColorTable (line 410) | protected int[] readColorTable(int ncolors) {
    method readContents (line 437) | protected void readContents() {
    method readGraphicControlExt (line 482) | protected void readGraphicControlExt() {
    method readHeader (line 496) | protected void readHeader() {
    method readImage (line 512) | protected void readImage() {
    method readLSD (line 571) | protected void readLSD() {
    method readNetscapeExt (line 585) | protected void readNetscapeExt() {
    method readShort (line 598) | protected int readShort() {
    method resetFrame (line 603) | protected void resetFrame() {
    method skip (line 620) | protected void skip() {

FILE: app/src/main/java/com/rance/chatui/util/GlobalOnItemClickManagerUtils.java
  class GlobalOnItemClickManagerUtils (line 16) | public class GlobalOnItemClickManagerUtils {
    method getInstance (line 22) | public static GlobalOnItemClickManagerUtils getInstance(Context contex...
    method attachToEditText (line 34) | public void attachToEditText(EditText editText) {
    method getOnItemClickListener (line 38) | public AdapterView.OnItemClickListener getOnItemClickListener() {

FILE: app/src/main/java/com/rance/chatui/util/MediaManager.java
  class MediaManager (line 12) | public class MediaManager {
    method playSound (line 23) | public static void playSound(final String filePath, final OnCompletion...
    method pause (line 52) | public static void pause() {
    method resume (line 62) | public static void resume() {
    method release (line 72) | public static void release() {

FILE: app/src/main/java/com/rance/chatui/util/MessageCenter.java
  class MessageCenter (line 20) | public class MessageCenter {
    method handleIncoming (line 42) | public static void handleIncoming(Bundle bundle, String mimeType, Acti...

FILE: app/src/main/java/com/rance/chatui/util/PhotoUtils.java
  class PhotoUtils (line 20) | public class PhotoUtils {
    method takePicture (line 29) | public static void takePicture(BaseFragment baseFragment, Uri imageUri...
    method cropImageUri (line 51) | public static void cropImageUri(Activity activity, Uri orgUri, Uri des...
    method openPic (line 77) | public static void openPic(Activity activity, int requestCode) {

FILE: app/src/main/java/com/rance/chatui/util/PopupWindowFactory.java
  class PopupWindowFactory (line 15) | public class PopupWindowFactory {
    method PopupWindowFactory (line 25) | public PopupWindowFactory(Context mContext, View view){
    method PopupWindowFactory (line 35) | public PopupWindowFactory(Context mContext, View view, int width, int ...
    method init (line 40) | private void init(Context mContext, View view, int width, int heigth){
    method getPopupWindow (line 77) | public PopupWindow getPopupWindow(){
    method showAtLocation (line 89) | public void showAtLocation(View parent, int gravity, int x, int y){
    method showAsDropDown (line 102) | public void showAsDropDown(View anchor){
    method showAsDropDown (line 112) | public void showAsDropDown(View anchor, int xoff, int yoff){
    method dismiss (line 123) | public void dismiss(){

FILE: app/src/main/java/com/rance/chatui/util/Utils.java
  class Utils (line 20) | public class Utils {
    method dp2px (line 28) | public static int dp2px(Context context, float dp) {
    method getEmotionContent (line 41) | public static SpannableString getEmotionContent(final Context context,...
    method getCurrentTime (line 74) | public static String getCurrentTime() {
    method long2String (line 80) | public static String long2String(long time) {
    method formatTime (line 106) | public static String formatTime(Long ms) {

FILE: app/src/main/java/com/rance/chatui/widget/BubbleDrawable.java
  class BubbleDrawable (line 22) | public class BubbleDrawable extends Drawable {
    method BubbleDrawable (line 35) | private BubbleDrawable(Builder builder) {
    method onBoundsChange (line 47) | @Override
    method draw (line 52) | @Override
    method getOpacity (line 57) | @Override
    method setAlpha (line 62) | @Override
    method setColorFilter (line 67) | @Override
    method setUpPath (line 72) | private void setUpPath(ArrowLocation mArrowLocation, Path path){
    method setUp (line 89) | private void setUp(Canvas canvas){
    method setUpLeftPath (line 109) | private void setUpLeftPath(RectF rect, Path path){
    method setUpTopPath (line 130) | private void setUpTopPath(RectF rect, Path path){
    method setUpRightPath (line 153) | private void setUpRightPath(RectF rect, Path path){
    method setUpBottomPath (line 176) | private void setUpBottomPath(RectF rect, Path path){
    method setUpShaderMatrix (line 200) | private void setUpShaderMatrix() {
    method getIntrinsicWidth (line 214) | @Override
    method getIntrinsicHeight (line 219) | @Override
    class Builder (line 224) | public static class Builder{
      method rect (line 240) | public Builder rect(RectF rect){
      method arrowWidth (line 245) | public Builder arrowWidth(float mArrowWidth){
      method angle (line 250) | public Builder angle(float mAngle){
      method arrowHeight (line 255) | public Builder arrowHeight(float mArrowHeight){
      method arrowPosition (line 260) | public Builder arrowPosition(float mArrowPosition){
      method bubbleColor (line 265) | public Builder bubbleColor(int bubbleColor){
      method bubbleBitmap (line 271) | public Builder bubbleBitmap(Bitmap bubbleBitmap){
      method arrowLocation (line 277) | public Builder arrowLocation(ArrowLocation arrowLocation){
      method bubbleType (line 282) | public Builder bubbleType(BubbleType bubbleType){
      method build (line 287) | public BubbleDrawable build(){
    type ArrowLocation (line 295) | public enum ArrowLocation{
      method ArrowLocation (line 303) | ArrowLocation(int value){
      method mapIntToValue (line 307) | public static ArrowLocation mapIntToValue(int stateInt) {
      method getDefault (line 316) | public static ArrowLocation getDefault(){
      method getIntValue (line 320) | public int getIntValue() {
    type BubbleType (line 325) | public enum BubbleType{

FILE: app/src/main/java/com/rance/chatui/widget/BubbleImageView.java
  class BubbleImageView (line 22) | public class BubbleImageView extends ImageView {
    method BubbleImageView (line 31) | public BubbleImageView(Context context) {
    method BubbleImageView (line 36) | public BubbleImageView(Context context, AttributeSet attrs) {
    method BubbleImageView (line 41) | public BubbleImageView(Context context, AttributeSet attrs, int defSty...
    method initView (line 46) | private void initView(AttributeSet attrs){
    method onMeasure (line 63) | @Override
    method onSizeChanged (line 76) | @Override
    method onLayout (line 84) | @Override
    method onDraw (line 90) | @Override
    method setUp (line 99) | private void setUp(int left, int right, int top, int bottom){
    method setUp (line 122) | private void setUp(int width, int height){
    method setUp (line 127) | private void setUp(){
    method setImageBitmap (line 148) | @Override
    method setImageDrawable (line 158) | @Override
    method setImageResource (line 167) | @Override
    method getDrawable (line 172) | private Drawable getDrawable(int res){
    method getBitmapFromDrawable (line 179) | private Bitmap getBitmapFromDrawable(Drawable drawable) {
    method getBitmapFromDrawable (line 183) | public static Bitmap getBitmapFromDrawable(Context mContext, Drawable ...
    method dp2px (line 208) | public static int dp2px(Context context, int dp) {

FILE: app/src/main/java/com/rance/chatui/widget/BubbleLinearLayout.java
  class BubbleLinearLayout (line 16) | public class BubbleLinearLayout extends LinearLayout {
    method BubbleLinearLayout (line 24) | public BubbleLinearLayout(Context context) {
    method BubbleLinearLayout (line 29) | public BubbleLinearLayout(Context context, AttributeSet attrs) {
    method initView (line 35) | private void initView(AttributeSet attrs){
    method onSizeChanged (line 54) | @Override
    method setUp (line 62) | private void setUp(int left, int right, int top, int bottom){
    method setUp (line 78) | private void setUp(int width, int height){

FILE: app/src/main/java/com/rance/chatui/widget/ChatContextMenu.java
  class ChatContextMenu (line 29) | public class ChatContextMenu extends RelativePopupWindow {
    method ChatContextMenu (line 33) | public ChatContextMenu(Context context, MessageInfo messageInfo) {
    method transitContent (line 67) | private void transitContent() {
    method copyToClipboard (line 71) | private void copyToClipboard() {
    method showOnAnchor (line 77) | @Override
    method circularReveal (line 85) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)

FILE: app/src/main/java/com/rance/chatui/widget/EmotionInputDetector.java
  class EmotionInputDetector (line 38) | public class EmotionInputDetector {
    method EmotionInputDetector (line 60) | private EmotionInputDetector() {
    method with (line 63) | public static EmotionInputDetector with(Activity activity) {
    method bindToContent (line 71) | public EmotionInputDetector bindToContent(View contentView) {
    method bindToEditText (line 76) | public EmotionInputDetector bindToEditText(EditText editText) {
    method bindToEmotionButton (line 123) | public EmotionInputDetector bindToEmotionButton(View emotionButton) {
    method bindToAddButton (line 154) | public EmotionInputDetector bindToAddButton(View addButton) {
    method bindToSendButton (line 186) | public EmotionInputDetector bindToSendButton(View sendButton) {
    method bindToVoiceButton (line 203) | public EmotionInputDetector bindToVoiceButton(final ImageView voiceBut...
    method bindToVoiceText (line 225) | public EmotionInputDetector bindToVoiceText(TextView voiceText) {
    method wantToCancel (line 275) | private boolean wantToCancel(int x, int y) {
    method setEmotionView (line 287) | public EmotionInputDetector setEmotionView(View emotionView) {
    method setViewPager (line 292) | public EmotionInputDetector setViewPager(ViewPager viewPager) {
    method build (line 297) | public EmotionInputDetector build() {
    method interceptBackPress (line 340) | public boolean interceptBackPress() {
    method showEmotionLayout (line 348) | private void showEmotionLayout() {
    method hideEmotionLayout (line 359) | public void hideEmotionLayout(boolean showSoftInput) {
    method lockContentHeight (line 368) | private void lockContentHeight() {
    method unlockContentHeightDelayed (line 375) | private void unlockContentHeightDelayed() {
    method showSoftInput (line 384) | private void showSoftInput() {
    method hideSoftInput (line 394) | public void hideSoftInput() {
    method isSoftInputShown (line 398) | private boolean isSoftInputShown() {
    method getSupportSoftInputHeight (line 402) | private int getSupportSoftInputHeight() {
    method getSoftButtonsBarHeight (line 421) | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)

FILE: app/src/main/java/com/rance/chatui/widget/GifTextView.java
  class GifTextView (line 25) | public class GifTextView extends AppCompatTextView {
    class SpanInfo (line 36) | private class SpanInfo {
      method SpanInfo (line 41) | public SpanInfo() {
    method GifTextView (line 62) | @SuppressLint("NewApi")
    method GifTextView (line 68) | @SuppressLint("NewApi")
    method GifTextView (line 74) | @SuppressLint("NewApi")
    method parseText (line 86) | private boolean parseText(String inputStr) {
    method parseBmp (line 120) | @SuppressWarnings("unused")
    method parseGif (line 144) | private void parseGif(int resourceId, int start, int end) {
    method setSpanText (line 170) | public void setSpanText(Handler handler, final String text, boolean is...
    method parseMessage (line 183) | public boolean parseMessage(GifTextView gifTextView) {
    method startPost (line 239) | public void startPost() {
    class TextRunnable (line 244) | public static final class TextRunnable implements Runnable {
      method TextRunnable (line 247) | public TextRunnable(GifTextView f) {
      method run (line 251) | @Override

FILE: app/src/main/java/com/rance/chatui/widget/IndicatorView.java
  class IndicatorView (line 18) | public class IndicatorView extends LinearLayout {
    method IndicatorView (line 27) | public IndicatorView(Context context) {
    method IndicatorView (line 31) | public IndicatorView(Context context, AttributeSet attrs) {
    method IndicatorView (line 35) | public IndicatorView(Context context, AttributeSet attrs, int defStyle...
    method initIndicator (line 47) | public void initIndicator(int count) {
    method playByStartPointToNext (line 70) | public void playByStartPointToNext(int startPosition, int nextPosition) {

FILE: app/src/main/java/com/rance/chatui/widget/NoScrollViewPager.java
  class NoScrollViewPager (line 12) | public class NoScrollViewPager extends ViewPager {
    method NoScrollViewPager (line 14) | public NoScrollViewPager(Context context) {
    method NoScrollViewPager (line 18) | public NoScrollViewPager(Context context, AttributeSet attrs) {
    method onTouchEvent (line 22) | @Override
    method onInterceptTouchEvent (line 27) | @Override

FILE: app/src/main/java/com/rance/chatui/widget/StateButton.java
  class StateButton (line 22) | public class StateButton extends AppCompatButton {
    method StateButton (line 60) | public StateButton(Context context) {
    method StateButton (line 64) | public StateButton(Context context, AttributeSet attrs) {
    method StateButton (line 68) | public StateButton(Context context, AttributeSet attrs, int defStyleAt...
    method setup (line 73) | private void setup(AttributeSet attrs) {
    method onMeasure (line 147) | @Override
    method setNormalStrokeColor (line 155) | public void setNormalStrokeColor(@ColorInt int normalStrokeColor) {
    method setPressedStrokeColor (line 160) | public void setPressedStrokeColor(@ColorInt int pressedStrokeColor) {
    method setUnableStrokeColor (line 165) | public void setUnableStrokeColor(@ColorInt int unableStrokeColor) {
    method setStateStrokeColor (line 170) | public void setStateStrokeColor(@ColorInt int normal, @ColorInt int pr...
    method setNormalStrokeWidth (line 179) | public void setNormalStrokeWidth(int normalStrokeWidth) {
    method setPressedStrokeWidth (line 184) | public void setPressedStrokeWidth(int pressedStrokeWidth) {
    method setUnableStrokeWidth (line 189) | public void setUnableStrokeWidth(int unableStrokeWidth) {
    method setStateStrokeWidth (line 194) | public void setStateStrokeWidth(int normal, int pressed, int unable){
    method setStrokeDash (line 201) | public void setStrokeDash(float strokeDashWidth, float strokeDashGap) {
    method setStroke (line 207) | private void setStroke(){
    method setStroke (line 213) | private void setStroke(GradientDrawable mBackground, int mStrokeColor,...
    method setRadius (line 219) | public void setRadius(@FloatRange(from = 0) float radius) {
    method setRound (line 226) | public void setRound(boolean round){
    method setRadius (line 234) | public void setRadius(float[] radii){
    method setStateBackgroundColor (line 242) | public void setStateBackgroundColor(@ColorInt int normal, @ColorInt in...
    method setNormalBackgroundColor (line 251) | public void setNormalBackgroundColor(@ColorInt int normalBackgroundCol...
    method setPressedBackgroundColor (line 256) | public void setPressedBackgroundColor(@ColorInt int pressedBackgroundC...
    method setUnableBackgroundColor (line 261) | public void setUnableBackgroundColor(@ColorInt int unableBackgroundCol...
    method setAnimationDuration (line 267) | public void setAnimationDuration(@IntRange(from = 0)int duration){
    method setTextColor (line 274) | private void setTextColor() {
    method setStateTextColor (line 280) | public void setStateTextColor(@ColorInt int normal, @ColorInt int pres...
    method setNormalTextColor (line 287) | public void setNormalTextColor(@ColorInt int normalTextColor) {
    method setPressedTextColor (line 293) | public void setPressedTextColor(@ColorInt int pressedTextColor) {
    method setUnableTextColor (line 298) | public void setUnableTextColor(@ColorInt int unableTextColor) {

FILE: app/src/test/java/com/rance/chatui/ExampleUnitTest.java
  class ExampleUnitTest (line 10) | public class ExampleUnitTest {
    method addition_isCorrect (line 11) | @Test
Condensed preview — 94 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (327K chars).
[
  {
    "path": ".gitignore",
    "chars": 760,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
  },
  {
    "path": ".idea/.name",
    "chars": 6,
    "preview": "ChatUI"
  },
  {
    "path": ".idea/compiler.xml",
    "chars": 686,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <resourceExt"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 74,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"\" />\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 159,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" chars"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 2226,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EntryPointsManager\">\n    <entry_points v"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 349,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 167,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 313,
    "preview": "修改自开源项目:https://github.com/Rance935/ChatUI \n\n## 修改内容\n+ 替换部分图片资源\n+ 用源生RecyclerView替换了EasyRecyclerView,比较相信官方。\n+ 修复拍照在7.0系"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 1029,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 24\n    buildToolsVersion \"24.0.2\"\n\n    aaptOpti"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 679,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:"
  },
  {
    "path": "app/src/androidTest/java/com/rance/chatui/ApplicationTest.java",
    "chars": 347,
    "preview": "package com.rance.chatui;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"htt"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 2883,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/ChatAdapter.java",
    "chars": 3865,
    "preview": "package com.rance.chatui.adapter;\n\nimport android.os.Handler;\nimport android.support.v7.widget.RecyclerView;\nimport andr"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/CommonFragmentPagerAdapter.java",
    "chars": 704,
    "preview": "package com.rance.chatui.adapter;\n\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.FragmentManager"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/ContactAdapter.java",
    "chars": 2253,
    "preview": "package com.rance.chatui.adapter;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nim"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/EmotionGridViewAdapter.java",
    "chars": 1798,
    "preview": "package com.rance.chatui.adapter;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGro"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/EmotionPagerAdapter.java",
    "chars": 932,
    "preview": "package com.rance.chatui.adapter;\n\nimport android.support.v4.view.PagerAdapter;\nimport android.support.v4.view.ViewPager"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/BaseViewHolder.java",
    "chars": 352,
    "preview": "package com.rance.chatui.adapter.holder;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\n/**\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/ChatAcceptViewHolder.java",
    "chars": 13454,
    "preview": "package com.rance.chatui.adapter.holder;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.text"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/ChatSendViewHolder.java",
    "chars": 14195,
    "preview": "package com.rance.chatui.adapter.holder;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.supp"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/base/BaseFragment.java",
    "chars": 1883,
    "preview": "package com.rance.chatui.base;\n\n\nimport android.app.Activity;\nimport android.app.ProgressDialog;\nimport android.os.Bundl"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/base/MyApplication.java",
    "chars": 1080,
    "preview": "package com.rance.chatui.base;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.util.Disp"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/FullImageInfo.java",
    "chars": 985,
    "preview": "package com.rance.chatui.enity;\n\n/**\n * 作者:Rance on 2016/12/21 16:22\n * 邮箱:rance935@163.com\n */\npublic class FullImageIn"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/IMContact.java",
    "chars": 730,
    "preview": "package com.rance.chatui.enity;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/3.\n */\n\npublic class IMContact {\n    String"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/Link.java",
    "chars": 690,
    "preview": "package com.rance.chatui.enity;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/4.\n */\n\npublic class Link {\n    String subj"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/MessageInfo.java",
    "chars": 2620,
    "preview": "package com.rance.chatui.enity;\n\n/**\n * 作者:Rance on 2016/12/14 14:13\n * 邮箱:rance935@163.com\n */\npublic class MessageInfo"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/ContactActivity.java",
    "chars": 5514,
    "preview": "package com.rance.chatui.ui.activity;\n\nimport android.Manifest;\nimport android.app.AlertDialog;\nimport android.content.D"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/FullImageActivity.java",
    "chars": 4860,
    "preview": "package com.rance.chatui.ui.activity;\n\nimport android.animation.ObjectAnimator;\nimport android.annotation.TargetApi;\nimp"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/IMActivity.java",
    "chars": 15169,
    "preview": "package com.rance.chatui.ui.activity;\n\nimport android.content.Intent;\nimport android.graphics.drawable.AnimationDrawable"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/fragment/ChatEmotionFragment.java",
    "chars": 5011,
    "preview": "package com.rance.chatui.ui.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport andr"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/fragment/ChatFunctionFragment.java",
    "chars": 11814,
    "preview": "package com.rance.chatui.ui.fragment;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Inte"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/AudioRecorderUtils.java",
    "chars": 5594,
    "preview": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.media.MediaRecorder;\nimport android.os.En"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/CheckPermissionUtils.java",
    "chars": 2705,
    "preview": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.c"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/Constants.java",
    "chars": 982,
    "preview": "package com.rance.chatui.util;\n\n/**\n * 作者:Rance on 2016/12/20 16:51\n * 邮箱:rance935@163.com\n */\npublic class Constants {\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/EmotionUtils.java",
    "chars": 19154,
    "preview": "\npackage com.rance.chatui.util;\n\nimport com.rance.chatui.R;\n\nimport java.util.LinkedHashMap;\n\n/**\n * 作者:Rance on 2016/11"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/FileUtils.java",
    "chars": 6510,
    "preview": "package com.rance.chatui.util;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/GifOpenHelper.java",
    "chars": 14273,
    "preview": "package com.rance.chatui.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\n\nimport java.io.I"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/GlobalOnItemClickManagerUtils.java",
    "chars": 2530,
    "preview": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.view.KeyEvent;\nimport android.view.View;\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/MediaManager.java",
    "chars": 1935,
    "preview": "package com.rance.chatui.util;\n\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.medi"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/MessageCenter.java",
    "chars": 4496,
    "preview": "package com.rance.chatui.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.Uri;\nimpo"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/PhotoUtils.java",
    "chars": 2690,
    "preview": "package com.rance.chatui.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitm"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/PopupWindowFactory.java",
    "chars": 3159,
    "preview": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.view.KeyEvent;\nimport android.view.Motion"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/Utils.java",
    "chars": 3764,
    "preview": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.gra"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleDrawable.java",
    "chars": 11577,
    "preview": "package com.rance.chatui.widget;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapShader;\nimport android.g"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleImageView.java",
    "chars": 7271,
    "preview": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android."
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleLinearLayout.java",
    "chars": 3012,
    "preview": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android."
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/ChatContextMenu.java",
    "chars": 3931,
    "preview": "package com.rance.chatui.widget;\n\nimport android.animation.Animator;\nimport android.annotation.TargetApi;\nimport android"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/EmotionInputDetector.java",
    "chars": 15904,
    "preview": "package com.rance.chatui.widget;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.conte"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/GifTextView.java",
    "chars": 8646,
    "preview": "package com.rance.chatui.widget;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/IndicatorView.java",
    "chars": 2286,
    "preview": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view."
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/NoScrollViewPager.java",
    "chars": 675,
    "preview": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.support.v4.view.ViewPager;\nimport andro"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/StateButton.java",
    "chars": 11798,
    "preview": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport andr"
  },
  {
    "path": "app/src/main/res/drawable/bg_circle_gary.xml",
    "chars": 402,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:"
  },
  {
    "path": "app/src/main/res/drawable/bg_circle_white.xml",
    "chars": 388,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:"
  },
  {
    "path": "app/src/main/res/drawable/bg_surname.xml",
    "chars": 257,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/corners_edit.xml",
    "chars": 404,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:"
  },
  {
    "path": "app/src/main/res/drawable/corners_edit_white.xml",
    "chars": 397,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:"
  },
  {
    "path": "app/src/main/res/drawable/divider.xml",
    "chars": 140,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/record_microphone.xml",
    "chars": 388,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <ite"
  },
  {
    "path": "app/src/main/res/drawable/record_microphone_bj.xml",
    "chars": 243,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/voice_left.xml",
    "chars": 544,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/drawable/voice_right.xml",
    "chars": 548,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/layout/activity_contact.xml",
    "chars": 429,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/activity_full_image.xml",
    "chars": 461,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 682,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    "
  },
  {
    "path": "app/src/main/res/layout/dialog_contact.xml",
    "chars": 1648,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat_emotion.xml",
    "chars": 913,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat_function.xml",
    "chars": 2835,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/include_reply_layout.xml",
    "chars": 4052,
    "preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/r"
  },
  {
    "path": "app/src/main/res/layout/item_chat_accept.xml",
    "chars": 11382,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/item_chat_send.xml",
    "chars": 13466,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/item_contact.xml",
    "chars": 730,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/layout_microphone.xml",
    "chars": 1467,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/popup_context_menu.xml",
    "chars": 1193,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/values/attr.xml",
    "chars": 2075,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 按钮自定义属性 -->\n    <declare-styleable name=\"StateButton\">\n     "
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 846,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 599,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 69,
    "preview": "<resources>\n    <string name=\"app_name\">ChatUI</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 1840,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 358,
    "preview": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as s"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "chars": 173,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <external-"
  },
  {
    "path": "app/src/test/java/com/rance/chatui/ExampleUnitTest.java",
    "chars": 309,
    "preview": "package com.rance.chatui;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit tests, swit"
  },
  {
    "path": "build.gradle",
    "chars": 498,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 231,
    "preview": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 855,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2314,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "settings.gradle",
    "chars": 15,
    "preview": "include ':app'\n"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the stridercheng/chatui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 94 files (293.5 KB), approximately 71.7k tokens, and a symbol index with 418 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!