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
================================================
================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
================================================
FILE: .idea/encodings.xml
================================================
================================================
FILE: .idea/misc.xml
================================================
================================================
FILE: .idea/modules.xml
================================================
================================================
FILE: .idea/runConfigurations.xml
================================================
================================================
FILE: .idea/vcs.xml
================================================
================================================
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、图片、文档。
## 截图

## 问题
代码结构待优化
================================================
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;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
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 {
private onItemClickListener onItemClickListener;
public Handler handler;
private List messageInfoList;
public ChatAdapter(List 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 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 list;
public CommonFragmentPagerAdapter(FragmentManager fm, ArrayList 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
implements View.OnClickListener {
private List imContactList;
private OnContactClickListener mOnContactClickListener;
public ContactAdapter(List 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 emotionNames;
private int itemWidth;
public EmotionGridViewAdapter(Context context, List 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 gvs;
public EmotionPagerAdapter(List 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 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 {
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 {
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 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 fragments;
private ChatEmotionFragment chatEmotionFragment;
private ChatFunctionFragment chatFunctionFragment;
private CommonFragmentPagerAdapter adapter;
private ChatAdapter chatAdapter;
private LinearLayoutManager layoutManager;
private List 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 emotionViews = new ArrayList<>();
List 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 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 EMPTY_GIF_MAP;
public static LinkedHashMap 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 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();
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", "top-->" + top);
Log.d("setUp", "bottom-->" + bottom);
if (right <= left || bottom <= top)
return;
RectF rectF = new RectF(left, top, right, bottom);
if (sourceDrawable != null)
mBitmap = getBitmapFromDrawable(sourceDrawable);
bubbleDrawable = new BubbleDrawable.Builder()
.rect(rectF)
.arrowLocation(mArrowLocation)
.angle(mAngle)
.arrowHeight(mArrowHeight)
.arrowWidth(mArrowWidth)
.bubbleType(BubbleDrawable.BubbleType.BITMAP)
.arrowPosition(mArrowPosition)
.bubbleBitmap(mBitmap)
.build();
}
private void setUp(int width, int height){
setUp(getPaddingLeft(), width - getPaddingRight(),
getPaddingTop(), height - getPaddingBottom());
}
private void setUp(){
int width = getWidth();
int height = getHeight();
int scale;
if (width > 0 && height <= 0 && sourceDrawable != null){
if (sourceDrawable.getIntrinsicWidth() >= 0){
scale = width / sourceDrawable.getIntrinsicWidth();
height = scale * sourceDrawable.getIntrinsicHeight();
}
}
if (height > 0 && width <= 0 && sourceDrawable != null){
if (sourceDrawable.getIntrinsicHeight() >= 0){
scale = height / sourceDrawable.getIntrinsicHeight();
width = scale * sourceDrawable.getIntrinsicWidth();
}
}
setUp(width, height);
}
@Override
public void setImageBitmap(Bitmap mBitmap) {
if (mBitmap == null)
return;
this.mBitmap = mBitmap;
sourceDrawable = new BitmapDrawable(getResources(), mBitmap);
setUp();
super.setImageDrawable(bubbleDrawable);
}
@Override
public void setImageDrawable(Drawable drawable){
if (drawable == null )
return;
sourceDrawable = drawable;
setUp();
super.setImageDrawable(bubbleDrawable);
}
@Override
public void setImageResource(int res){
setImageDrawable(getDrawable(res));
}
private Drawable getDrawable(int res){
if (res == 0){
throw new IllegalArgumentException("getDrawable res can not be zero");
}
return getContext().getResources().getDrawable(res);
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
return getBitmapFromDrawable(getContext(), drawable, getWidth(), getWidth(), 25);
}
public static Bitmap getBitmapFromDrawable(Context mContext, Drawable drawable, int width, int height, int defaultSize) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (width > 0 && height > 0){
bitmap = Bitmap.createBitmap(width,
height, Bitmap.Config.ARGB_8888);
}else{
bitmap = Bitmap.createBitmap(dp2px(mContext, defaultSize),
dp2px(mContext, defaultSize), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
public static int dp2px(Context context, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/BubbleLinearLayout.java
================================================
package com.rance.chatui.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import com.rance.chatui.R;
/**
* 作者:Rance on 2016/12/15 10:49
* 邮箱:rance935@163.com
* 自定义聊天气泡LinearLayout
*/
public class BubbleLinearLayout extends LinearLayout {
private BubbleDrawable bubbleDrawable;
private float mArrowWidth;
private float mAngle;
private float mArrowHeight;
private float mArrowPosition;
private BubbleDrawable.ArrowLocation mArrowLocation;
private int bubbleColor;
public BubbleLinearLayout(Context context) {
super(context);
initView(null);
}
public BubbleLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
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);
bubbleColor = array.getColor(R.styleable.BubbleView_bubbleColor,
BubbleDrawable.Builder.DEFAULT_BUBBLE_COLOR);
int location = array.getInt(R.styleable.BubbleView_arrowLocation, 0);
mArrowLocation = BubbleDrawable.ArrowLocation.mapIntToValue(location);
array.recycle();
}
}
@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);
}
}
private void setUp(int left, int right, int top, int bottom){
if (right < left || bottom < top)
return;
RectF rectF = new RectF(left, top, right, bottom);
bubbleDrawable = new BubbleDrawable.Builder()
.rect(rectF)
.arrowLocation(mArrowLocation)
.bubbleType(BubbleDrawable.BubbleType.COLOR)
.angle(mAngle)
.arrowHeight(mArrowHeight)
.arrowWidth(mArrowWidth)
.arrowPosition(mArrowPosition)
.bubbleColor(bubbleColor)
.build();
}
private void setUp(int width, int height){
setUp(getPaddingLeft(), + width - getPaddingRight(),
getPaddingTop(), height - getPaddingBottom());
setBackgroundDrawable(bubbleDrawable);
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/ChatContextMenu.java
================================================
package com.rance.chatui.widget;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Message;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.TextView;
import com.labo.kaji.relativepopupwindow.RelativePopupWindow;
import com.rance.chatui.R;
import com.rance.chatui.enity.MessageInfo;
/**
* Created by chengz
*
* @date 2017/7/31.
*/
public class ChatContextMenu extends RelativePopupWindow {
private MessageInfo mMessageInfo;
private Context mContext;
public ChatContextMenu(Context context, MessageInfo messageInfo) {
this.mMessageInfo = messageInfo;
this.mContext = context;
View view = LayoutInflater.from(context).inflate(R.layout.popup_context_menu, null);
setContentView(view);
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setFocusable(true);
setOutsideTouchable(true);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// Disable default animation for circular reveal
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setAnimationStyle(0);
}
TextView tvCopy = (TextView) view.findViewById(R.id.tv_copy);
TextView tvTransit = (TextView) view.findViewById(R.id.tv_transit);
tvCopy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyToClipboard();
}
});
tvTransit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
transitContent();
}
});
}
private void transitContent() {
}
private void copyToClipboard() {
ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData data = ClipData.newPlainText("Moa", mMessageInfo.getContent());
cm.setPrimaryClip(data);
}
@Override
public void showOnAnchor(@NonNull View anchor, int vertPos, int horizPos, int x, int y, boolean fitInScreen) {
super.showOnAnchor(anchor, vertPos, horizPos, x, y, fitInScreen);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
circularReveal(anchor);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void circularReveal(@NonNull final View anchor) {
final View contentView = getContentView();
contentView.post(new Runnable() {
@Override
public void run() {
final int[] myLocation = new int[2];
final int[] anchorLocation = new int[2];
contentView.getLocationOnScreen(myLocation);
anchor.getLocationOnScreen(anchorLocation);
final int cx = anchorLocation[0] - myLocation[0] + anchor.getWidth()/2;
final int cy = anchorLocation[1] - myLocation[1] + anchor.getHeight()/2;
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
final int dx = Math.max(cx, contentView.getMeasuredWidth() - cx);
final int dy = Math.max(cy, contentView.getMeasuredHeight() - cy);
final float finalRadius = (float) Math.hypot(dx, dy);
Animator animator = ViewAnimationUtils.createCircularReveal(contentView, cx, cy, 0f, finalRadius);
animator.setDuration(1);
animator.start();
}
});
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/EmotionInputDetector.java
================================================
package com.rance.chatui.widget;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.ViewPager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.rance.chatui.R;
import com.rance.chatui.enity.MessageInfo;
import com.rance.chatui.util.AudioRecorderUtils;
import com.rance.chatui.util.Constants;
import com.rance.chatui.util.PopupWindowFactory;
import com.rance.chatui.util.Utils;
import org.greenrobot.eventbus.EventBus;
/**
* 作者:Rance on 2016/12/13 15:19
* 邮箱:rance935@163.com
* 输入框管理类
*/
public class EmotionInputDetector {
private static final String TAG = "EmotionInputDetector";
private static final String SHARE_PREFERENCE_NAME = "com.dss886.emotioninputdetector";
private static final String SHARE_PREFERENCE_TAG = "soft_input_height";
private Activity mActivity;
private InputMethodManager mInputManager;
private SharedPreferences sp;
private View mEmotionLayout;
private EditText mEditText;
private TextView mVoiceText;
private View mContentView;
private ViewPager mViewPager;
private View mSendButton;
private View mAddButton;
private Boolean isShowEmotion = false;
private Boolean isShowAdd = false;
private boolean isShowVoice = false;
private AudioRecorderUtils mAudioRecorderUtils;
private PopupWindowFactory mVoicePop;
private TextView mPopVoiceText;
private EmotionInputDetector() {
}
public static EmotionInputDetector with(Activity activity) {
EmotionInputDetector emotionInputDetector = new EmotionInputDetector();
emotionInputDetector.mActivity = activity;
emotionInputDetector.mInputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
emotionInputDetector.sp = activity.getSharedPreferences(SHARE_PREFERENCE_NAME, Context.MODE_PRIVATE);
return emotionInputDetector;
}
public EmotionInputDetector bindToContent(View contentView) {
mContentView = contentView;
return this;
}
public EmotionInputDetector bindToEditText(EditText editText) {
mEditText = editText;
mEditText.requestFocus();
mEditText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) {
lockContentHeight();
hideEmotionLayout(true);
mEditText.postDelayed(new Runnable() {
@Override
public void run() {
unlockContentHeightDelayed();
}
}, 200L);
}
return false;
}
});
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 0) {
mAddButton.setVisibility(View.GONE);
mSendButton.setVisibility(View.VISIBLE);
} else {
mAddButton.setVisibility(View.VISIBLE);
mSendButton.setVisibility(View.GONE);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
return this;
}
public EmotionInputDetector bindToEmotionButton(View emotionButton) {
emotionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEmotionLayout.isShown()) {
if (isShowAdd) {
mViewPager.setCurrentItem(0);
isShowEmotion = true;
isShowAdd = false;
} else {
lockContentHeight();
hideEmotionLayout(true);
isShowEmotion = false;
unlockContentHeightDelayed();
}
} else {
if (isSoftInputShown()) {
lockContentHeight();
showEmotionLayout();
unlockContentHeightDelayed();
} else {
showEmotionLayout();
}
mViewPager.setCurrentItem(0);
isShowEmotion = true;
}
}
});
return this;
}
public EmotionInputDetector bindToAddButton(View addButton) {
mAddButton = addButton;
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEmotionLayout.isShown()) {
if (isShowEmotion) {
mViewPager.setCurrentItem(1);
isShowAdd = true;
isShowEmotion = false;
} else {
lockContentHeight();
hideEmotionLayout(true);
isShowAdd = false;
unlockContentHeightDelayed();
}
} else {
if (isSoftInputShown()) {
lockContentHeight();
showEmotionLayout();
unlockContentHeightDelayed();
} else {
showEmotionLayout();
}
mViewPager.setCurrentItem(1);
isShowAdd = true;
}
}
});
return this;
}
public EmotionInputDetector bindToSendButton(View sendButton) {
mSendButton = sendButton;
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAddButton.setVisibility(View.VISIBLE);
mSendButton.setVisibility(View.GONE);
MessageInfo messageInfo = new MessageInfo();
messageInfo.setContent(mEditText.getText().toString());
messageInfo.setFileType(Constants.CHAT_FILE_TYPE_TEXT);
EventBus.getDefault().post(messageInfo);
mEditText.setText("");
}
});
return this;
}
public EmotionInputDetector bindToVoiceButton(final ImageView voiceButton) {
voiceButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isShowVoice) {
voiceButton.setImageResource(R.mipmap.icon_voice);
} else {
voiceButton.setImageResource(R.mipmap.icon_keyboard);
}
isShowVoice = !isShowVoice;
hideEmotionLayout(false);
hideSoftInput();
mVoiceText.setVisibility(mVoiceText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
mEditText.setVisibility(mVoiceText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
}
});
return this;
}
public EmotionInputDetector bindToVoiceText(TextView voiceText) {
mVoiceText = voiceText;
mVoiceText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 获得x轴坐标
int x = (int) event.getX();
// 获得y轴坐标
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mVoicePop.showAtLocation(v, Gravity.CENTER, 0, 0);
mVoiceText.setText("松开结束");
mPopVoiceText.setText("手指上滑,取消发送");
mVoiceText.setTag("1");
mAudioRecorderUtils.startRecord(mActivity);
break;
case MotionEvent.ACTION_MOVE:
if (wantToCancel(x, y)) {
mVoiceText.setText("松开结束");
mPopVoiceText.setText("松开手指,取消发送");
mVoiceText.setTag("2");
} else {
mVoiceText.setText("松开结束");
mPopVoiceText.setText("手指上滑,取消发送");
mVoiceText.setTag("1");
}
break;
case MotionEvent.ACTION_UP:
mVoicePop.dismiss();
if (mVoiceText.getTag().equals("2")) {
//取消录音(删除录音文件)
mAudioRecorderUtils.cancelRecord();
} else {
//结束录音(保存录音文件)
mAudioRecorderUtils.stopRecord();
}
mVoiceText.setText("按住说话");
mVoiceText.setTag("3");
mVoiceText.setVisibility(View.GONE);
mEditText.setVisibility(View.VISIBLE);
break;
}
return true;
}
});
return this;
}
private boolean wantToCancel(int x, int y) {
// 超过按钮的宽度
if (x < 0 || x > mVoiceText.getWidth()) {
return true;
}
// 超过按钮的高度
if (y < -50 || y > mVoiceText.getHeight() + 50) {
return true;
}
return false;
}
public EmotionInputDetector setEmotionView(View emotionView) {
mEmotionLayout = emotionView;
return this;
}
public EmotionInputDetector setViewPager(ViewPager viewPager) {
mViewPager = viewPager;
return this;
}
public EmotionInputDetector build() {
mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN |
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
hideSoftInput();
mAudioRecorderUtils = new AudioRecorderUtils();
View view = View.inflate(mActivity, R.layout.layout_microphone, null);
mVoicePop = new PopupWindowFactory(mActivity, view);
//PopupWindow布局文件里面的控件
final ImageView mImageView = (ImageView) view.findViewById(R.id.iv_recording_icon);
final TextView mTextView = (TextView) view.findViewById(R.id.tv_recording_time);
mPopVoiceText = (TextView) view.findViewById(R.id.tv_recording_text);
//录音回调
mAudioRecorderUtils.setOnAudioStatusUpdateListener(new AudioRecorderUtils.OnAudioStatusUpdateListener() {
//录音中....db为声音分贝,time为录音时长
@Override
public void onUpdate(double db, long time) {
mImageView.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
mTextView.setText(Utils.long2String(time));
}
//录音结束,filePath为保存路径
@Override
public void onStop(long time, String filePath) {
mTextView.setText(Utils.long2String(0));
MessageInfo messageInfo = new MessageInfo();
messageInfo.setFileType(Constants.CHAT_FILE_TYPE_VOICE);
messageInfo.setFilepath(filePath);
messageInfo.setVoiceTime(time);
EventBus.getDefault().post(messageInfo);
}
@Override
public void onError() {
mVoiceText.setVisibility(View.GONE);
mEditText.setVisibility(View.VISIBLE);
}
});
return this;
}
public boolean interceptBackPress() {
if (mEmotionLayout.isShown()) {
hideEmotionLayout(false);
return true;
}
return false;
}
private void showEmotionLayout() {
int softInputHeight = getSupportSoftInputHeight();
if (softInputHeight == 0) {
softInputHeight = sp.getInt(SHARE_PREFERENCE_TAG, 768);
}
hideSoftInput();
Log.e(TAG, "showEmotionLayout: ->" + softInputHeight );
mEmotionLayout.getLayoutParams().height = softInputHeight;
mEmotionLayout.setVisibility(View.VISIBLE);
}
public void hideEmotionLayout(boolean showSoftInput) {
if (mEmotionLayout.isShown()) {
mEmotionLayout.setVisibility(View.GONE);
if (showSoftInput) {
showSoftInput();
}
}
}
private void lockContentHeight() {
Log.e(TAG, "lockContentHeight: ->" + mContentView.getHeight());
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
params.height = mContentView.getHeight();
params.weight = 0.0F;
}
private void unlockContentHeightDelayed() {
mEditText.postDelayed(new Runnable() {
@Override
public void run() {
((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
}
}, 200L);
}
private void showSoftInput() {
mEditText.requestFocus();
mEditText.post(new Runnable() {
@Override
public void run() {
mInputManager.showSoftInput(mEditText, 0);
}
});
}
public void hideSoftInput() {
mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}
private boolean isSoftInputShown() {
return getSupportSoftInputHeight() != 0;
}
private int getSupportSoftInputHeight() {
Rect r = new Rect();
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();
int softInputHeight = screenHeight - r.bottom;
if (Build.VERSION.SDK_INT >= 20) {
// When SDK Level >= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has)
softInputHeight = softInputHeight - getSoftButtonsBarHeight();
}
if (softInputHeight < 0) {
Log.w("EmotionInputDetector", "Warning: value of softInputHeight is below zero!");
}
if (softInputHeight > 0) {
Log.e(TAG, "getSupportSoftInputHeight: ->" + softInputHeight );
sp.edit().putInt(SHARE_PREFERENCE_TAG, softInputHeight).apply();
}
return softInputHeight;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private int getSoftButtonsBarHeight() {
DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int usableHeight = metrics.heightPixels;
mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int realHeight = metrics.heightPixels;
if (realHeight > usableHeight) {
return realHeight - usableHeight;
} else {
return 0;
}
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/GifTextView.java
================================================
package com.rance.chatui.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.v7.widget.AppCompatTextView;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.widget.EditText;
import android.widget.TextView;
import com.rance.chatui.util.EmotionUtils;
import com.rance.chatui.util.GifOpenHelper;
import com.rance.chatui.util.Utils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GifTextView extends AppCompatTextView {
/**
* 注:如果获取的gif帧与帧之间的时间间隔都不相同,建议调个固定的,最好的方法是将gif图的间隔设置相同
*/
private static final int DELAYED = 600;
/**
* @author Dragon SpanInfo
* 类用于存储一个要显示的图片(动态或静态)的信息,包括分解后的每一帧mapList、替代文字的起始位置、终止位置
* 、帧的总数、当前需要显示的帧、帧与帧之间的时间间隔
*/
private class SpanInfo {
ArrayList mapList;
@SuppressWarnings("unused")
int start, end, frameCount, currentFrameIndex, delay;
public SpanInfo() {
mapList = new ArrayList();
start = end = frameCount = currentFrameIndex = delay = 0;
}
}
/**
* spanInfoList 是一个SpanInfo的list ,用于处理一个TextView中出现多个要匹配的图片的情况
*/
private ArrayList spanInfoList = null;
private Handler handler; // 用于处理从子线程TextView传来的消息
private String myText; // 存储textView应该显示的文本
/**
* 这三个构造方法一个也不要少,否则会产生CastException,注意在这三个构造函数中都为spanInfoList实例化,可能有些浪费
* ,但保证不会有空指针异常
*
* @param context
* @param attrs
* @param defStyle
*/
@SuppressLint("NewApi")
public GifTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
GifTextView.this.setFocusableInTouchMode(false);
}
@SuppressLint("NewApi")
public GifTextView(Context context, AttributeSet attrs) {
super(context, attrs);
GifTextView.this.setFocusableInTouchMode(false);
}
@SuppressLint("NewApi")
public GifTextView(Context context) {
super(context);
GifTextView.this.setFocusableInTouchMode(false);
}
/**
* 对要显示在textView上的文本进行解析,看看是否文本中有需要与Gif或者静态图片匹配的文本 若有,那么调用parseGif
* 对该文本对应的Gif图片进行解析 或者嗲用parseBmp解析静态图片
*
* @param inputStr
*/
private boolean parseText(String inputStr) {
myText = inputStr;
String regexEmotion = "\\[([\u4e00-\u9fa5\\w])+\\]";
Pattern patternEmotion = Pattern.compile(regexEmotion);
Matcher mMatcher = patternEmotion.matcher(inputStr);
boolean hasGif = false;
while (mMatcher.find()) {
String faceName = mMatcher.group();
Integer faceId = null;
/**
* 这里匹配时用到了图片库,即一个专门存放图片id和其匹配的名称的静态对象,这两个静态对象放在了FaceData.java
* 中,并采用了静态块的方法进行了初始化,不会有空指针异常
*/
if ((faceId = EmotionUtils.EMPTY_GIF_MAP.get(faceName)) != null) {
if (isGif) {
parseGif(faceId, mMatcher.start(), mMatcher.end());
} else {
parseBmp(faceId, mMatcher.start(), mMatcher.end());
}
}
hasGif = true;
}
return hasGif;
}
/**
* 对静态图片进行解析:
* 创建一个SpanInfo对象,帧数设为1,按照下面的参数设置,最后不要忘记将SpanInfo对象添加进spanInfoList中, 否则不会显示
*
* @param resourceId
* @param start
* @param end
*/
@SuppressWarnings("unused")
private void parseBmp(int resourceId, int start, int end) {
Bitmap bitmap = BitmapFactory.decodeResource(getContext()
.getResources(), resourceId);
ImageSpan imageSpan = new ImageSpan(getContext(), bitmap);
SpanInfo spanInfo = new SpanInfo();
spanInfo.currentFrameIndex = 0;
spanInfo.frameCount = 1;
spanInfo.start = start;
spanInfo.end = end;
spanInfo.delay = 100;
spanInfo.mapList.add(bitmap);
spanInfoList.add(spanInfo);
}
/**
* 解析Gif图片,与静态图片唯一的不同是这里需要调用GifOpenHelper类读取Gif返回一系一组bitmap(用for 循环把这一
* 组的bitmap存储在SpanInfo.mapList中,此时的frameCount参数也大于1了)
*
* @param resourceId
* @param start
* @param end
*/
private void parseGif(int resourceId, int start, int end) {
GifOpenHelper helper = new GifOpenHelper();
helper.read(getContext().getResources().openRawResource(resourceId));
SpanInfo spanInfo = new SpanInfo();
spanInfo.currentFrameIndex = 0;
spanInfo.frameCount = helper.getFrameCount();
spanInfo.start = start;
spanInfo.end = end;
spanInfo.mapList.add(helper.getImage());
for (int i = 1; i < helper.getFrameCount(); i++) {
spanInfo.mapList.add(helper.nextBitmap());
}
spanInfo.delay = helper.nextDelay(); // 获得每一帧之间的延迟
spanInfoList.add(spanInfo);
}
private boolean isGif;
/**
* GifTextView 与外部对象的接口,以后设置文本内容时使用setSpanText() 而不再是setText();
*
* @param handler
* @param text
*/
public void setSpanText(Handler handler, final String text, boolean isGif) {
this.handler = handler; // 获得UI的Handler
this.isGif = isGif;
spanInfoList = new ArrayList();
if (parseText(text)) {// 对String对象进行解析
if (parseMessage(this)) {
startPost();
}
} else {
setText(myText);
}
}
public boolean parseMessage(GifTextView gifTextView) {
if (gifTextView.myText != null && !gifTextView.myText.equals("")) {
SpannableString sb = new SpannableString("" + gifTextView.myText); // 获得要显示的文本
int gifCount = 0;
SpanInfo info = null;
for (int i = 0; i < gifTextView.spanInfoList.size(); i++) { // for循环,处理显示多个图片的问题
info = gifTextView.spanInfoList.get(i);
if (info.mapList.size() > 1) {
/*
* gifCount用来区分是Gif还是BMP,若是gif gifCount>0
* ,否则gifCount=0
*/
gifCount++;
}
Bitmap bitmap = info.mapList
.get(info.currentFrameIndex);
info.currentFrameIndex = (info.currentFrameIndex + 1)
% (info.frameCount);
/**
* currentFrameIndex
* 用于控制当前应该显示的帧的序号,每次显示之后currentFrameIndex 应该加1
* ,加到frameCount后再变成0循环显示
*/
int size = Utils.dp2px(gifTextView.getContext(), 30);
if (gifCount != 0) {
bitmap = Bitmap.createScaledBitmap(bitmap, size,
size, true);
} else {
bitmap = Bitmap.createScaledBitmap(bitmap, size,
size, true);
}
ImageSpan imageSpan = new ImageSpan(gifTextView.getContext(),
bitmap);
if (info.end <= sb.length()) {
sb.setSpan(imageSpan, info.start, info.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
break;
}
}
// 对所有的图片对应的ImageSpan完成设置后,调用TextView的setText方法设置文本
gifTextView.setText(sb);
if (gifCount != 0) {
return true;
} else {
return false;
}
}
return false;
}
public TextRunnable rTextRunnable;
public void startPost() {
rTextRunnable = new TextRunnable(this); // 生成Runnable对象
handler.post(rTextRunnable); // 利用UI线程的Handler 将r添加进消息队列中。
}
public static final class TextRunnable implements Runnable {
private final WeakReference mWeakReference;
public TextRunnable(GifTextView f) {
mWeakReference = new WeakReference(f);
}
@Override
public void run() {
GifTextView gifTextView = mWeakReference.get();
if (gifTextView != null) {
/**
* 这一步是为了节省内存而是用,即如果文本中只有静态图片没有动态图片,那么该线程就此终止,不会重复执行
* 。而如果有动图,那么会一直执行
*/
if (gifTextView.parseMessage(gifTextView)) {
gifTextView.handler.postDelayed(this, DELAYED);
}
}
}
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/IndicatorView.java
================================================
package com.rance.chatui.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import com.rance.chatui.R;
import com.rance.chatui.util.Utils;
import java.util.ArrayList;
/**
* 作者:Rance on 2016/11/29 10:47
* 邮箱:rance935@163.com
* 自定义表情底部指示器
*/
public class IndicatorView extends LinearLayout {
private Context mContext;
private ArrayList mImageViews;//所有指示器集合
private int size = 6;
private int marginSize = 15;
private int pointSize;//指示器的大小
private int marginLeft;//间距
public IndicatorView(Context context) {
this(context, null);
}
public IndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
pointSize = Utils.dp2px(context, size);
marginLeft = Utils.dp2px(context, marginSize);
}
/**
* 初始化指示器
*
* @param count 指示器的数量
*/
public void initIndicator(int count) {
mImageViews = new ArrayList<>();
this.removeAllViews();
LayoutParams lp;
for (int i = 0; i < count; i++) {
View v = new View(mContext);
lp = new LayoutParams(pointSize, pointSize);
if (i != 0)
lp.leftMargin = marginLeft;
v.setLayoutParams(lp);
if (i == 0) {
v.setBackgroundResource(R.drawable.bg_circle_white);
} else {
v.setBackgroundResource(R.drawable.bg_circle_gary);
}
mImageViews.add(v);
this.addView(v);
}
}
/**
* 页面移动时切换指示器
*/
public void playByStartPointToNext(int startPosition, int nextPosition) {
if (startPosition < 0 || nextPosition < 0 || nextPosition == startPosition) {
startPosition = nextPosition = 0;
}
final View ViewStrat = mImageViews.get(startPosition);
final View ViewNext = mImageViews.get(nextPosition);
ViewNext.setBackgroundResource(R.drawable.bg_circle_white);
ViewStrat.setBackgroundResource(R.drawable.bg_circle_gary);
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/NoScrollViewPager.java
================================================
package com.rance.chatui.widget;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* 作者:Rance on 2016/11/25 16:55
* 邮箱:rance935@163.com
*/
public class NoScrollViewPager extends ViewPager {
public NoScrollViewPager(Context context) {
super(context);
}
public NoScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent arg0) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
return false;
}
}
================================================
FILE: app/src/main/java/com/rance/chatui/widget/StateButton.java
================================================
package com.rance.chatui.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;
import com.rance.chatui.R;
/**
* 作者:Rance on 2016/11/18 16:53
* 邮箱:rance935@163.com
* 自定义按钮 详情请参考https://github.com/niniloveyou/StateButton
*/
public class StateButton extends AppCompatButton {
//text color
private int mNormalTextColor = 0;
private int mPressedTextColor = 0;
private int mUnableTextColor = 0;
ColorStateList mTextColorStateList;
//animation duration
private int mDuration = 0;
//radius
private float mRadius = 0;
private boolean mRound;
//stroke
private float mStrokeDashWidth = 0;
private float mStrokeDashGap = 0;
private int mNormalStrokeWidth = 0;
private int mPressedStrokeWidth = 0;
private int mUnableStrokeWidth = 0;
private int mNormalStrokeColor = 0;
private int mPressedStrokeColor = 0;
private int mUnableStrokeColor = 0;
//background color
private int mNormalBackgroundColor = 0;
private int mPressedBackgroundColor = 0;
private int mUnableBackgroundColor = 0;
private GradientDrawable mNormalBackground;
private GradientDrawable mPressedBackground;
private GradientDrawable mUnableBackground;
private int[][] states;
StateListDrawable mStateBackground;
public StateButton(Context context) {
this(context, null);
}
public StateButton(Context context, AttributeSet attrs) {
this(context, attrs, android.support.v7.appcompat.R.attr.buttonStyle);
}
public StateButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup(attrs);
}
private void setup(AttributeSet attrs) {
states = new int[4][];
Drawable drawable = getBackground();
if(drawable != null && drawable instanceof StateListDrawable){
mStateBackground = (StateListDrawable) drawable;
}else{
mStateBackground = new StateListDrawable();
}
mNormalBackground = new GradientDrawable();
mPressedBackground = new GradientDrawable();
mUnableBackground = new GradientDrawable();
//pressed, focused, normal, unable
states[0] = new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled };
states[1] = new int[] { android.R.attr.state_enabled, android.R.attr.state_focused };
states[2] = new int[] { android.R.attr.state_enabled };
states[3] = new int[] { android.R.attr.state_window_focused };
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StateButton);
//get original text color as default
//set text color
mTextColorStateList = getTextColors();
int mDefaultNormalTextColor = mTextColorStateList.getColorForState(states[2], getCurrentTextColor());
int mDefaultPressedTextColor = mTextColorStateList.getColorForState(states[0], getCurrentTextColor());
int mDefaultUnableTextColor = mTextColorStateList.getColorForState(states[3], getCurrentTextColor());
mNormalTextColor = a.getColor(R.styleable.StateButton_normalTextColor, mDefaultNormalTextColor);
mPressedTextColor = a.getColor(R.styleable.StateButton_pressedTextColor, mDefaultPressedTextColor);
mUnableTextColor = a.getColor(R.styleable.StateButton_unableTextColor, mDefaultUnableTextColor);
setTextColor();
//set animation duration
mDuration = a.getInteger(R.styleable.StateButton_animationDuration, mDuration);
mStateBackground.setEnterFadeDuration(mDuration);
//set background color
mNormalBackgroundColor = a.getColor(R.styleable.StateButton_normalBackgroundColor, 0);
mPressedBackgroundColor = a.getColor(R.styleable.StateButton_pressedBackgroundColor, 0);
mUnableBackgroundColor = a.getColor(R.styleable.StateButton_unableBackgroundColor, 0);
mNormalBackground.setColor(mNormalBackgroundColor);
mPressedBackground.setColor(mPressedBackgroundColor);
mUnableBackground.setColor(mUnableBackgroundColor);
//set radius
mRadius = a.getDimensionPixelSize(R.styleable.StateButton_radius, 0);
mRound = a.getBoolean(R.styleable.StateButton_round, false);
mNormalBackground.setCornerRadius(mRadius);
mPressedBackground.setCornerRadius(mRadius);
mUnableBackground.setCornerRadius(mRadius);
//set stroke
mStrokeDashWidth = a.getDimensionPixelSize(R.styleable.StateButton_strokeDashWidth, 0);
mStrokeDashGap = a.getDimensionPixelSize(R.styleable.StateButton_strokeDashWidth, 0);
mNormalStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_normalStrokeWidth, 0);
mPressedStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_pressedStrokeWidth, 0);
mUnableStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_unableStrokeWidth, 0);
mNormalStrokeColor = a.getColor(R.styleable.StateButton_normalStrokeColor, 0);
mPressedStrokeColor = a.getColor(R.styleable.StateButton_pressedStrokeColor, 0);
mUnableStrokeColor = a.getColor(R.styleable.StateButton_unableStrokeColor, 0);
setStroke();
//set background
mStateBackground.addState(states[0], mPressedBackground);
mStateBackground.addState(states[1], mPressedBackground);
mStateBackground.addState(states[2], mNormalBackground);
mStateBackground.addState(states[3], mUnableBackground);
setBackgroundDrawable(mStateBackground);
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setRound(mRound);
}
/****************** stroke color *********************/
public void setNormalStrokeColor(@ColorInt int normalStrokeColor) {
this.mNormalStrokeColor = normalStrokeColor;
setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);
}
public void setPressedStrokeColor(@ColorInt int pressedStrokeColor) {
this.mPressedStrokeColor = pressedStrokeColor;
setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);
}
public void setUnableStrokeColor(@ColorInt int unableStrokeColor) {
this.mUnableStrokeColor = unableStrokeColor;
setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);
}
public void setStateStrokeColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){
mNormalStrokeColor = normal;
mPressedStrokeColor = pressed;
mUnableStrokeColor = unable;
setStroke();
}
/****************** stroke width *********************/
public void setNormalStrokeWidth(int normalStrokeWidth) {
this.mNormalStrokeWidth = normalStrokeWidth;
setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);
}
public void setPressedStrokeWidth(int pressedStrokeWidth) {
this.mPressedStrokeWidth = pressedStrokeWidth;
setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);
}
public void setUnableStrokeWidth(int unableStrokeWidth) {
this.mUnableStrokeWidth = unableStrokeWidth;
setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);
}
public void setStateStrokeWidth(int normal, int pressed, int unable){
mNormalStrokeWidth = normal;
mPressedStrokeWidth = pressed;
mUnableStrokeWidth= unable;
setStroke();
}
public void setStrokeDash(float strokeDashWidth, float strokeDashGap) {
this.mStrokeDashWidth = strokeDashWidth;
this.mStrokeDashGap = strokeDashWidth;
setStroke();
}
private void setStroke(){
setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);
setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);
setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);
}
private void setStroke(GradientDrawable mBackground, int mStrokeColor, int mStrokeWidth) {
mBackground.setStroke(mStrokeWidth, mStrokeColor, mStrokeDashWidth, mStrokeDashGap);
}
/******************** radius *******************************/
public void setRadius(@FloatRange(from = 0) float radius) {
this.mRadius = radius;
mNormalBackground.setCornerRadius(mRadius);
mPressedBackground.setCornerRadius(mRadius);
mUnableBackground.setCornerRadius(mRadius);
}
public void setRound(boolean round){
this.mRound = round;
int height = getMeasuredHeight();
if(mRound){
setRadius(height / 2f);
}
}
public void setRadius(float[] radii){
mNormalBackground.setCornerRadii(radii);
mPressedBackground.setCornerRadii(radii);
mUnableBackground.setCornerRadii(radii);
}
/******************** background color **********************/
public void setStateBackgroundColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){
mPressedBackgroundColor = normal;
mNormalBackgroundColor = pressed;
mUnableBackgroundColor = unable;
mNormalBackground.setColor(mNormalBackgroundColor);
mPressedBackground.setColor(mPressedBackgroundColor);
mUnableBackground.setColor(mUnableBackgroundColor);
}
public void setNormalBackgroundColor(@ColorInt int normalBackgroundColor) {
this.mNormalBackgroundColor = normalBackgroundColor;
mNormalBackground.setColor(mNormalBackgroundColor);
}
public void setPressedBackgroundColor(@ColorInt int pressedBackgroundColor) {
this.mPressedBackgroundColor = pressedBackgroundColor;
mPressedBackground.setColor(mPressedBackgroundColor);
}
public void setUnableBackgroundColor(@ColorInt int unableBackgroundColor) {
this.mUnableBackgroundColor = unableBackgroundColor;
mUnableBackground.setColor(mUnableBackgroundColor);
}
/*******************alpha animation duration********************/
public void setAnimationDuration(@IntRange(from = 0)int duration){
this.mDuration = duration;
mStateBackground.setEnterFadeDuration(mDuration);
}
/*************** text color ***********************/
private void setTextColor() {
int[] colors = new int[] {mPressedTextColor, mPressedTextColor, mNormalTextColor, mUnableTextColor};
mTextColorStateList = new ColorStateList(states, colors);
setTextColor(mTextColorStateList);
}
public void setStateTextColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){
this.mNormalTextColor = normal;
this.mPressedTextColor = pressed;
this.mUnableTextColor = unable;
setTextColor();
}
public void setNormalTextColor(@ColorInt int normalTextColor) {
this.mNormalTextColor = normalTextColor;
setTextColor();
}
public void setPressedTextColor(@ColorInt int pressedTextColor) {
this.mPressedTextColor = pressedTextColor;
setTextColor();
}
public void setUnableTextColor(@ColorInt int unableTextColor) {
this.mUnableTextColor = unableTextColor;
setTextColor();
}
}
================================================
FILE: app/src/main/res/drawable/bg_circle_gary.xml
================================================
================================================
FILE: app/src/main/res/drawable/bg_circle_white.xml
================================================
================================================
FILE: app/src/main/res/drawable/bg_surname.xml
================================================
================================================
FILE: app/src/main/res/drawable/corners_edit.xml
================================================
================================================
FILE: app/src/main/res/drawable/corners_edit_white.xml
================================================
================================================
FILE: app/src/main/res/drawable/divider.xml
================================================
================================================
FILE: app/src/main/res/drawable/record_microphone.xml
================================================
-
================================================
FILE: app/src/main/res/drawable/record_microphone_bj.xml
================================================
================================================
FILE: app/src/main/res/drawable/voice_left.xml
================================================
================================================
FILE: app/src/main/res/drawable/voice_right.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_contact.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_full_image.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/layout/dialog_contact.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_chat_emotion.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_chat_function.xml
================================================
================================================
FILE: app/src/main/res/layout/include_reply_layout.xml
================================================
================================================
FILE: app/src/main/res/layout/item_chat_accept.xml
================================================
================================================
FILE: app/src/main/res/layout/item_chat_send.xml
================================================
================================================
FILE: app/src/main/res/layout/item_contact.xml
================================================
================================================
FILE: app/src/main/res/layout/layout_microphone.xml
================================================
================================================
FILE: app/src/main/res/layout/popup_context_menu.xml
================================================
================================================
FILE: app/src/main/res/values/attr.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#3F51B5
#FFFFFF
#000000
#f3f3f3
#d9d9d9
#1b1b1b
#818181
#919191
#c7c7c7
#404040
#E4F4FE
#000000
#EBEFF0
#000000
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
16dp
16dp
10dp
10dp
5dp
19sp
17sp
12sp
13sp
1dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
ChatUI
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: app/src/main/res/xml/file_paths.xml
================================================
================================================
FILE: app/src/test/java/com/rance/chatui/ExampleUnitTest.java
================================================
package com.rance.chatui;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app'