├── .gitignore ├── ChatRecyclerView ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── piasy │ └── chatrecyclerview │ └── ChatRecyclerView.java ├── LICENSE ├── README.md ├── art └── chat-recycler-view-demo.gif ├── build.gradle ├── example ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── piasy │ │ └── chatrecyclerview │ │ └── example │ │ ├── BlankFragment.java │ │ ├── InputDialog.java │ │ ├── InputDialogFragment.java │ │ ├── MainActivity.java │ │ ├── OverlayFragment.java │ │ └── UnderFragment.java │ └── res │ ├── drawable-xhdpi │ └── demo.jpeg │ ├── drawable │ └── round_cornor_bg.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_blank.xml │ ├── fragment_input_dialog.xml │ ├── fragment_overlay.xml │ ├── fragment_under.xml │ ├── ui_input.xml │ └── ui_item.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | **.iml 2 | .idea 3 | .gradle 4 | /local.properties 5 | bintray.properties 6 | .DS_Store 7 | **/build 8 | /captures 9 | -------------------------------------------------------------------------------- /ChatRecyclerView/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: "https://raw.githubusercontent.com/Piasy/BintrayUploadScript/master/bintray.gradle" 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 6 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode rootProject.ext.releaseVersionCode 12 | versionName rootProject.ext.releaseVersionName 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile "com.android.support:recyclerview-v7:${rootProject.ext.androidSupportSdkVersion}" 24 | } 25 | -------------------------------------------------------------------------------- /ChatRecyclerView/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/piasy/tools/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ChatRecyclerView/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ChatRecyclerView/src/main/java/com/github/piasy/chatrecyclerview/ChatRecyclerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview; 26 | 27 | import android.content.Context; 28 | import android.support.annotation.Nullable; 29 | import android.support.v7.widget.LinearLayoutManager; 30 | import android.support.v7.widget.RecyclerView; 31 | import android.util.AttributeSet; 32 | import android.view.ViewTreeObserver; 33 | 34 | /** 35 | * Created by Piasy{github.com/Piasy} on 5/25/16. 36 | */ 37 | public class ChatRecyclerView extends RecyclerView { 38 | 39 | private long mLastScrollTime; 40 | private boolean mIsIdleFromDrag; 41 | private int mNewMessagePosition; 42 | private long mAutoScrollTimeout; 43 | private boolean mAutoScrollOnUserLeave; 44 | private Runnable mAutoScroll; 45 | /** 46 | * There is a bug, in Xiaomi MI NOTE LTE, and One plus 3, YOLO's live room chat, 47 | * when use want to input text, we show a dialog fragment, when this dialog fragment shows, 48 | * the recycler view show its first item, without triggering the scroll callback. 49 | * 50 | * So we observe the global layout changes, and when we are showing the first item 51 | * (last visible position is item count - 1), we force scroll to bottom immediately. 52 | */ 53 | private ViewTreeObserver.OnGlobalLayoutListener mScroll2TopFix; 54 | private volatile int mPendingMsgCount; 55 | 56 | public ChatRecyclerView(Context context) { 57 | this(context, null); 58 | } 59 | 60 | public ChatRecyclerView(Context context, @Nullable AttributeSet attrs) { 61 | this(context, attrs, -1); 62 | } 63 | 64 | public ChatRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 65 | super(context, attrs, defStyle); 66 | } 67 | 68 | public void initAutoScroll(int newMessagePosition, long timeout, 69 | boolean autoScrollOnUserLeave) { 70 | mNewMessagePosition = newMessagePosition; 71 | mAutoScrollTimeout = timeout; 72 | mAutoScrollOnUserLeave = autoScrollOnUserLeave; 73 | 74 | if (mAutoScrollOnUserLeave) { 75 | mAutoScroll = new Runnable() { 76 | @Override 77 | public void run() { 78 | if (mPendingMsgCount > 0) { 79 | // notifyItem*** is problematic, causing 80 | // `java.lang.IndexOutOfBoundsException: Inconsistency detected. 81 | // Invalid view holder adapter` 82 | // use notifyDataSetChanged instead 83 | getAdapter().notifyDataSetChanged(); 84 | scrollToPosition(mNewMessagePosition); 85 | mPendingMsgCount = 0; 86 | } else if (getAdapter().getItemCount() > 0) { 87 | smoothScrollToPosition(mNewMessagePosition); 88 | } 89 | } 90 | }; 91 | } 92 | mScroll2TopFix = new ViewTreeObserver.OnGlobalLayoutListener() { 93 | @Override 94 | public void onGlobalLayout() { 95 | if (getLayoutManager() instanceof LinearLayoutManager) { 96 | int lastVisiblePos = ((LinearLayoutManager) getLayoutManager()) 97 | .findLastVisibleItemPosition() + 1; 98 | if (lastVisiblePos == getAdapter().getItemCount()) { 99 | mAutoScroll.run(); 100 | } 101 | } 102 | } 103 | }; 104 | 105 | addOnScrollListener(new OnScrollListener() { 106 | @Override 107 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 108 | super.onScrollStateChanged(recyclerView, newState); 109 | switch (newState) { 110 | case SCROLL_STATE_DRAGGING: 111 | mIsIdleFromDrag = true; 112 | break; 113 | case SCROLL_STATE_IDLE: 114 | if (mIsIdleFromDrag) { 115 | mLastScrollTime = System.currentTimeMillis(); 116 | mIsIdleFromDrag = false; 117 | if (mAutoScrollOnUserLeave) { 118 | removeCallbacks(mAutoScroll); 119 | postDelayed(mAutoScroll, mAutoScrollTimeout); 120 | } 121 | } 122 | break; 123 | default: 124 | break; 125 | } 126 | } 127 | }); 128 | 129 | getViewTreeObserver().addOnGlobalLayoutListener(mScroll2TopFix); 130 | } 131 | 132 | public void notifyNewMessage() { 133 | if (System.currentTimeMillis() - mLastScrollTime > mAutoScrollTimeout) { 134 | if (mPendingMsgCount > 0) { 135 | // notifyItem*** is problematic, causing 136 | // `java.lang.IndexOutOfBoundsException: Inconsistency detected. 137 | // Invalid view holder adapter` 138 | // use notifyDataSetChanged instead 139 | getAdapter().notifyDataSetChanged(); 140 | scrollToPosition(mNewMessagePosition); 141 | mPendingMsgCount = 0; 142 | } else if (mPendingMsgCount == 0) { 143 | // normal case, we can have animation :) 144 | getAdapter().notifyItemInserted(mNewMessagePosition); 145 | scrollToPosition(mNewMessagePosition); 146 | } else if (getAdapter().getItemCount() > 0) { 147 | smoothScrollToPosition(mNewMessagePosition); 148 | } 149 | removeCallbacks(mAutoScroll); 150 | } else { 151 | mPendingMsgCount++; 152 | } 153 | } 154 | 155 | @Override 156 | protected void onDetachedFromWindow() { 157 | super.onDetachedFromWindow(); 158 | if (mAutoScroll != null) { 159 | removeCallbacks(mAutoScroll); 160 | } 161 | if (mScroll2TopFix != null) { 162 | getViewTreeObserver().removeOnGlobalLayoutListener(mScroll2TopFix); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Piasy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatRecyclerView 2 | Implement your user friendly chat RecyclerView in one line! 3 | 4 | ## 缘由 5 | 对于一个有聊天功能的 APP 来说, 如果来了新消息, 那当然应该滚动到最新的消息, 但是如果用户此时正在查看以前的消息, 6 | 那强行滚动到最新消息是不是会让用户很抓狂呢? 最理想的效果就是: 如果用户当前正在翻看历史消息, 来了新消息就不要 7 | 自动滚动到最新消息处, 如果用户停止翻看历史消息一段时间, 再来了新消息, 就自动滚动到最新消息处。当然你也可以更激进, 8 | 用户停止翻看历史消息一段时间之后, 即便没有新消息, 也自动滚动到最新消息处。 9 | 10 | ## 效果 11 | 12 | ![效果图](art/chat-recycler-view-demo.gif) 13 | 14 | ## 用法 15 | 16 | ### 添加依赖 17 | ``` gradle 18 | allprojects { 19 | repositories { 20 | maven { 21 | url "http://dl.bintray.com/piasy/maven" 22 | } 23 | } 24 | } 25 | 26 | compile 'com.github.piasy:ChatRecyclerView:1.2.0' 27 | ``` 28 | 29 | ### 代码使用 30 | layout 代码: 31 | 32 | ``` xml 33 | 39 | ``` 40 | 41 | java 代码: 42 | 43 | ``` java 44 | // 三个参数依次是: 新消息塞进去的 position、用户停止翻看历史消息多少 ms 之后自动滚动、 45 | // 是否没有新消息也超时滚动 46 | // 在为 recycler view 进行设置 adapter 等初始化时调用 47 | chatRecyclerView.initAutoScroll(0, 3000, true); 48 | 49 | // 在为 adapter 添加新数据之后调用, 注意, 你不要调用任何 adapter.notify*** 方法, 50 | // 否则效果会有问题 51 | chatRecyclerView.notifyNewMessage(); 52 | ``` 53 | 54 | ## 注意事项 55 | 如果没有给 adapter 加入新数据, 请不要调用 `notifyNewMessage` 函数, 否则可能造成以下闪退。 56 | 57 | ``` java 58 | IndexOutOfBoundsException: Inconsistency detected. Invalid item position 59 | ``` 60 | 61 | 如果你需要把 recycler view 滚动到指定位置, 你可以调用 `scrollToPosition` 或者 `smoothScrollToPosition`。 62 | -------------------------------------------------------------------------------- /art/chat-recycler-view-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Piasy/ChatRecyclerView/34d454e7371e99a17cf55c30083ed14225fa14c4/art/chat-recycler-view-demo.gif -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.0' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.1' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | } 18 | } 19 | 20 | task clean(type: Delete) { 21 | delete rootProject.buildDir 22 | } 23 | 24 | ext { 25 | userName = 'Piasy' 26 | developer = [ 27 | id : 'piasy', 28 | name : 'piasy', 29 | email: 'xz4215@gmail.com' 30 | ] 31 | license = [ 32 | id : 'MIT', 33 | name: 'The MIT License (MIT)', 34 | url : 'http://opensource.org/licenses/MIT' 35 | ] 36 | groupName = 'com.github.piasy' 37 | artifactName = 'ChatRecyclerView' 38 | artifactDescription = 'Implement your user friendly chat RecyclerView in one line!' 39 | artifactLabels = ['RecyclerView', 'chat'] 40 | releaseVersionCode = 4 41 | releaseVersionName = '1.2.1' 42 | 43 | androidCompileSdkVersion = 24 44 | androidBuildToolsVersion = '25.0.1' 45 | androidSupportSdkVersion = '25.0.1' 46 | minSdkVersion = 16 47 | targetSdkVersion = 24 48 | } 49 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 5 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "com.github.piasy.chatrecyclerview.example" 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode rootProject.ext.releaseVersionCode 12 | versionName rootProject.ext.releaseVersionName 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportSdkVersion}" 25 | compile project(':ChatRecyclerView') 26 | 27 | compile 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.0.0' 28 | } 29 | -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/piasy/tools/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/BlankFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.piasy.chatrecyclerview.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | import android.widget.TextView; 13 | import com.github.piasy.chatrecyclerview.ChatRecyclerView; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * A simple {@link Fragment} subclass. 19 | */ 20 | public class BlankFragment extends Fragment implements InputDialog.Action { 21 | 22 | private Adapter mAdapter; 23 | private ChatRecyclerView mChatRecyclerView; 24 | private Button mBtnAdd; 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | // Inflate the layout for this fragment 30 | return inflater.inflate(R.layout.fragment_blank, container, false); 31 | } 32 | 33 | @Override 34 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 35 | super.onViewCreated(view, savedInstanceState); 36 | 37 | mChatRecyclerView = (ChatRecyclerView) view.findViewById(R.id.mChatRv); 38 | mChatRecyclerView.setLayoutManager( 39 | new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true)); 40 | mAdapter = new Adapter(); 41 | mChatRecyclerView.setAdapter(mAdapter); 42 | mChatRecyclerView.initAutoScroll(0, 5000, true); 43 | 44 | //RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mChatRecyclerView 45 | // .getLayoutParams(); 46 | //params.height = dip2px(170); 47 | //params.topMargin = 0; 48 | //mChatRecyclerView.setLayoutParams(params); 49 | 50 | mBtnAdd = (Button) view.findViewById(R.id.mBtnAdd); 51 | view.findViewById(R.id.mBtnAdd).setOnClickListener(new View.OnClickListener() { 52 | @Override 53 | public void onClick(View v) { 54 | mBtnAdd.setVisibility(View.GONE); 55 | InputDialog inputDialog = new InputDialog(); 56 | inputDialog.setTargetFragment(BlankFragment.this, 1001); 57 | getFragmentManager() 58 | .beginTransaction() 59 | .add(inputDialog, "InputDialog") 60 | .commit(); 61 | } 62 | }); 63 | } 64 | 65 | public int dip2px(int dipValue) { 66 | float reSize = getResources().getDisplayMetrics().density; 67 | return (int) ((dipValue * reSize) + 0.5); 68 | } 69 | 70 | @Override 71 | public void send(String text) { 72 | mBtnAdd.setVisibility(View.VISIBLE); 73 | mAdapter.add(text); 74 | mChatRecyclerView.notifyNewMessage(); 75 | } 76 | 77 | static class Adapter extends RecyclerView.Adapter { 78 | List mItems = new ArrayList<>(); 79 | 80 | @Override 81 | public VH onCreateViewHolder(ViewGroup parent, int viewType) { 82 | return new VH(LayoutInflater.from(parent.getContext()) 83 | .inflate(R.layout.ui_item, parent, false)); 84 | } 85 | 86 | @Override 87 | public void onBindViewHolder(VH holder, int position) { 88 | holder.mTv.setText(mItems.get(position)); 89 | } 90 | 91 | @Override 92 | public int getItemCount() { 93 | return mItems.size(); 94 | } 95 | 96 | void add(String text) { 97 | mItems.add(0, text); 98 | } 99 | } 100 | 101 | static class VH extends RecyclerView.ViewHolder { 102 | final TextView mTv; 103 | 104 | public VH(View itemView) { 105 | super(itemView); 106 | mTv = (TextView) itemView; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/InputDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview.example; 26 | 27 | import android.content.Context; 28 | import android.content.res.Resources; 29 | import android.os.Bundle; 30 | import android.support.annotation.Nullable; 31 | import android.support.v4.app.DialogFragment; 32 | import android.view.Gravity; 33 | import android.view.LayoutInflater; 34 | import android.view.View; 35 | import android.view.ViewGroup; 36 | import android.view.Window; 37 | import android.view.WindowManager; 38 | import android.widget.EditText; 39 | import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEvent; 40 | import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEventListener; 41 | import net.yslibrary.android.keyboardvisibilityevent.util.UIUtil; 42 | 43 | /** 44 | * Created by Piasy{github.com/Piasy} on 6/3/16. 45 | */ 46 | public class InputDialog extends DialogFragment { 47 | 48 | private Action mAction; 49 | 50 | @Override 51 | public void onAttach(Context context) { 52 | super.onAttach(context); 53 | mAction = (Action) getTargetFragment(); 54 | } 55 | 56 | @Override 57 | public void onDetach() { 58 | super.onDetach(); 59 | mAction = null; 60 | } 61 | 62 | @Override 63 | public void onStart() { 64 | super.onStart(); 65 | // without title and title divider 66 | 67 | // Less dimmed background; see http://stackoverflow.com/q/13822842/56285 68 | Window window = getDialog().getWindow(); 69 | WindowManager.LayoutParams params = window.getAttributes(); 70 | //CHECKSTYLE:OFF 71 | params.dimAmount = 0; 72 | //CHECKSTYLE:ON 73 | window.setAttributes(params); 74 | 75 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 76 | window.setGravity(Gravity.BOTTOM); 77 | 78 | // Transparent background; see http://stackoverflow.com/q/15007272/56285 79 | // (Needed to make dialog's alpha shadow look good) 80 | window.setBackgroundDrawableResource(android.R.color.transparent); 81 | 82 | final Resources res = getResources(); 83 | final int titleDividerId = res.getIdentifier("titleDivider", "id", "android"); 84 | if (titleDividerId > 0) { 85 | final View titleDivider = getDialog().findViewById(titleDividerId); 86 | if (titleDivider != null) { 87 | titleDivider.setBackgroundColor(res.getColor(android.R.color.transparent)); 88 | } 89 | } 90 | } 91 | 92 | @Nullable 93 | @Override 94 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 95 | Bundle savedInstanceState) { 96 | getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); 97 | getDialog().setCanceledOnTouchOutside(true); 98 | return inflater.inflate(R.layout.ui_input, container, false); 99 | } 100 | 101 | @Override 102 | public void onViewCreated(View view, Bundle savedInstanceState) { 103 | super.onViewCreated(view, savedInstanceState); 104 | final EditText editText = (EditText) view.findViewById(R.id.mEtInput); 105 | view.findViewById(R.id.mBtnSend).setOnClickListener(new View.OnClickListener() { 106 | @Override 107 | public void onClick(View v) { 108 | mAction.send(editText.getText().toString()); 109 | dismiss(); 110 | } 111 | }); 112 | KeyboardVisibilityEvent.setEventListener(getActivity(), 113 | new KeyboardVisibilityEventListener() { 114 | @Override 115 | public void onVisibilityChanged(boolean isOpen) { 116 | if (!isOpen) { 117 | dismiss(); 118 | } 119 | } 120 | }); 121 | editText.post(new Runnable() { 122 | @Override 123 | public void run() { 124 | UIUtil.showKeyboard(getContext(), editText); 125 | } 126 | }); 127 | } 128 | 129 | public int dip2px(int dipValue) { 130 | float reSize = getResources().getDisplayMetrics().density; 131 | return (int) ((dipValue * reSize) + 0.5); 132 | } 133 | 134 | public interface Action { 135 | void send(String text); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/InputDialogFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview.example; 26 | 27 | import android.content.Context; 28 | import android.content.res.Resources; 29 | import android.os.Bundle; 30 | import android.support.v4.app.DialogFragment; 31 | import android.view.LayoutInflater; 32 | import android.view.View; 33 | import android.view.ViewGroup; 34 | import android.view.Window; 35 | import android.view.inputmethod.InputMethodManager; 36 | import android.widget.EditText; 37 | 38 | public class InputDialogFragment extends DialogFragment { 39 | 40 | public InputDialogFragment() { 41 | // Required empty public constructor 42 | } 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 46 | // Inflate the layout for this fragment 47 | getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); 48 | getDialog().setCanceledOnTouchOutside(true); 49 | return inflater.inflate(R.layout.fragment_input_dialog, container, false); 50 | } 51 | 52 | @Override 53 | public void onViewCreated(View view, Bundle savedInstanceState) { 54 | super.onViewCreated(view, savedInstanceState); 55 | final EditText paramEditText = (EditText) view.findViewById(R.id.mEditText); 56 | paramEditText.requestFocus(); 57 | paramEditText.post(new Runnable() { 58 | 59 | @Override 60 | public void run() { 61 | ((InputMethodManager) getActivity().getSystemService(Context 62 | .INPUT_METHOD_SERVICE)).showSoftInput(paramEditText, 0); 63 | } 64 | }); 65 | } 66 | 67 | @Override 68 | public void onStart() { 69 | super.onStart(); 70 | Window window = getDialog().getWindow(); 71 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 72 | window.setBackgroundDrawableResource(android.R.color.transparent); 73 | 74 | final Resources res = getResources(); 75 | final int titleDividerId = res.getIdentifier("titleDivider", "id", "android"); 76 | if (titleDividerId > 0) { 77 | final View titleDivider = getDialog().findViewById(titleDividerId); 78 | if (titleDivider != null) { 79 | titleDivider.setBackgroundColor(res.getColor(android.R.color.transparent)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview.example; 26 | 27 | import android.os.Bundle; 28 | import android.support.v7.app.AppCompatActivity; 29 | 30 | public class MainActivity extends AppCompatActivity { 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | 37 | // NOTE: example is broken 38 | getSupportFragmentManager().beginTransaction() 39 | .add(R.id.mLayer1, new UnderFragment()) 40 | //.add(R.id.mLayer2, new OverlayFragment()) 41 | .add(R.id.mLayer2, new BlankFragment()) 42 | .commit(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/OverlayFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview.example; 26 | 27 | import android.os.Bundle; 28 | import android.support.v4.app.Fragment; 29 | import android.view.LayoutInflater; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | 33 | public class OverlayFragment extends Fragment { 34 | 35 | public OverlayFragment() { 36 | // Required empty public constructor 37 | } 38 | 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 41 | Bundle savedInstanceState) { 42 | // Inflate the layout for this fragment 43 | return inflater.inflate(R.layout.fragment_overlay, container, false); 44 | } 45 | 46 | @Override 47 | public void onViewCreated(View view, Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | view.findViewById(R.id.mBtnClick).setOnClickListener(new View.OnClickListener() { 50 | @Override 51 | public void onClick(View v) { 52 | new InputDialogFragment() 53 | .show(getFragmentManager(), "InputDialogFragment"); 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/piasy/chatrecyclerview/example/UnderFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.chatrecyclerview.example; 26 | 27 | import android.os.Bundle; 28 | import android.support.v4.app.Fragment; 29 | import android.view.LayoutInflater; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | 33 | public class UnderFragment extends Fragment { 34 | 35 | public UnderFragment() { 36 | // Required empty public constructor 37 | } 38 | 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 41 | Bundle savedInstanceState) { 42 | // Inflate the layout for this fragment 43 | return inflater.inflate(R.layout.fragment_under, container, false); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/src/main/res/drawable-xhdpi/demo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Piasy/ChatRecyclerView/34d454e7371e99a17cf55c30083ed14225fa14c4/example/src/main/res/drawable-xhdpi/demo.jpeg -------------------------------------------------------------------------------- /example/src/main/res/drawable/round_cornor_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 29 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /example/src/main/res/layout/fragment_blank.xml: -------------------------------------------------------------------------------- 1 | 7 | 16 | 17 | 24 | 25 |