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、图片、文档。 ## 截图 ![image](https://github.com/stridercheng/chatui/raw/master/images/preview.png) ## 问题 代码结构待优化 ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "24.0.2" aaptOptions { cruncherEnabled = false } defaultConfig { applicationId "com.rance.chatui" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleCompatible compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:design:24.2.1' compile 'com.android.support:support-v4:24.2.1' compile 'org.greenrobot:eventbus:3.0.0' compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.labo.kaji:relativepopupwindow:0.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in C:\Users\Administrator\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/com/rance/chatui/ApplicationTest.java ================================================ package com.rance.chatui; import android.app.Application; import android.test.ApplicationTestCase; /** * 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'