├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── Demo.apk ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── arvin │ │ └── itemdecorationhelper │ │ └── sample │ │ ├── ContactAdapter.java │ │ ├── ContactEntity.java │ │ ├── GridActivity.java │ │ ├── HanziToPinyin.java │ │ ├── LinearActivity.java │ │ ├── MainActivity.java │ │ ├── StaggeredGridActivity.java │ │ └── TestData.java │ └── res │ ├── drawable-v21 │ └── bg_item.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── bg_item.xml │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_linear.xml │ ├── activity_main.xml │ ├── item_text.xml │ └── layout_section_header.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── itemdecorationhelper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── arvin │ │ └── itemdecorationhelper │ │ ├── BaseDividerItemDecoration.java │ │ ├── BaseStickyDividerItemDecoration.java │ │ ├── DefaultHeaderCallBack.java │ │ ├── GridDividerItemDecoration.java │ │ ├── GroupData.java │ │ ├── ItemDecorationFactory.java │ │ ├── ItemDecorationHelper.java │ │ ├── LinearDividerItemDecoration.java │ │ ├── OnHeaderClickListener.java │ │ ├── StaggeredGridDividerItemDecoration.java │ │ ├── StickyDividerCallback.java │ │ ├── StickyGridDividerItemDecoration.java │ │ ├── StickyHeaderClickGestureDetector.java │ │ └── StickyLinearDividerItemDecoration.java │ └── res │ ├── layout │ └── item_decoration_default_header.xml │ └── values │ └── strings.xml └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ItemDecorationHelper 2 | 3 | RecyclerView提供了良好的设计,将各部分功能的实现都拆解开,方便自定义,虽然加大了使用难度,但是却大大的增加了可扩展性,稍微会使用之后,扩展起来非常的舒心。 4 | 5 | **功能**:适用于以下几种布局的分割线和粘性头部 6 | 7 | * LinearLayoutManager 8 | * GridLayoutManager *(水平方向粘性头部暂未实现)* 9 | * StaggeredGridLayoutManager *(粘性头部暂未实现)* 10 | 11 | **对于分割线,与大多数的实现不同,在GridLayoutManager和StaggeredGridLayoutManager布局时是平分Item的,分割线大的时候就能发现** 12 | 13 | 先上效果图 14 | 15 | ![image.png](https://upload-images.jianshu.io/upload_images/3157525-f3d66efc623b2a4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 16 | 17 | ![image.png](https://upload-images.jianshu.io/upload_images/3157525-01f96bddaf73b96e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | *注意:其中这三种布局的分割线不管是水平还是垂直方向都是支持的,但是带有header的只支持LinearLayoutManager的水平和垂直以及GridLayoutManager垂直方向* 20 | 21 | 还可以[下载Demo](https://github.com/arvinljw/ItemDecorationHelper/tree/master/app/Demo.apk)在手机上预览一下。 22 | 23 | ### 使用 24 | 25 | [![](https://jitpack.io/v/arvinljw/ItemDecorationHelper.svg)](https://jitpack.io/#arvinljw/ItemDecorationHelper) 26 | 27 | **1、在根目录的build.gradle中加入如下配置** 28 | 29 | ``` 30 | allprojects { 31 | repositories { 32 | ... 33 | maven { url 'https://jitpack.io' } 34 | } 35 | } 36 | ``` 37 | 38 | **2、在要用的module中增加如下引用** 39 | 40 | ``` 41 | dependencies { 42 | ... 43 | implementation 'com.github.arvinljw:ItemDecorationHelper:v2.0.0' 44 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 45 | } 46 | ``` 47 | 48 | *其中recyclerview的版本换成自己app中的版本即可* 49 | 50 | **3、为RecyclerView添加ItemDecoration**,通过ItemDecorationFactory生成包括分割线的ItemDecoration和粘性头部的ItemDecoration,后者包含了分割线 51 | 52 | * ItemDecorationFactory.DividerBuilder 分割线 53 | 54 | ``` 55 | itemDecoration = new ItemDecorationFactory.DividerBuilder() 56 | .dividerHeight(2) 57 | .dividerColor(Color.parseColor("#D8D8D8")) 58 | .showLastDivider(false)//默认是true 59 | .build(recyclerView); 60 | recyclerView.addItemDecoration(itemDecoration); 61 | ``` 62 | 63 | * ItemDecorationFactory.StickyDividerBuilder 粘性头部 64 | 65 | ``` 66 | itemDecoration = new ItemDecorationFactory.StickyDividerBuilder() 67 | .dividerHeight(2) 68 | .dividerColor(Color.parseColor("#D8D8D8")) 69 | .callback(new StickyDividerCallback() { 70 | @Override 71 | public GroupData getGroupData(int position) { 72 | //生成GroupData 73 | return data; 74 | } 75 | @Override 76 | public View getStickyHeaderView(int position) { 77 | //...生成headerView 78 | return headerView; 79 | } 80 | }).build(recyclerView); 81 | recyclerView.addItemDecoration(itemDecoration); 82 | ``` 83 | 84 | 其中如果是粘性头部,需要自己生成GroupData和自定义View的设置,GroupData需要设置,**分组标题,在分组中的位置以及该分组的长度**;至于如何实现,需要根据数据格式定义不同的逻辑,可以参照[demo中的实现逻辑去获取](https://github.com/arvinljw/ItemDecorationHelper/blob/master/app/src/main/java/net/arvin/itemdecorationhelper/sample/LinearActivity.java),该类的getItemDecoration方法。 85 | 86 | **对于分割线没啥说的,可以设置分割线的颜色和大小。** 87 | 88 | #### 默认头部 89 | 90 | 其中对于显示分组头部,有一个默认实现DefaultHeaderCallBack,只需要重写分组数据即可。例如: 91 | 92 | ``` 93 | headerCallBack = new DefaultHeaderCallBack(this) { 94 | @Override 95 | public GroupData getGroupData(int position) { 96 | //生成GroupData 97 | return data; 98 | } 99 | }; 100 | ``` 101 | 102 | *注意如果添加在Adapter中添加了emptyView,那么这个时候其实是没有数据项的,所以在getGroupData方法中返回null即可* 103 | 104 | #### 头部点击事件 105 | 106 | 为头部增加点击事件,其中回调中的position是当前header所在的item的实际位置。 107 | 108 | ``` 109 | ((BaseStickyDividerItemDecoration) itemDecoration).setOnHeaderClickListener(new OnHeaderClickListener() { 110 | @Override 111 | public void onHeaderClicked(int position) { 112 | //TODO 113 | } 114 | }); 115 | ``` 116 | 117 | *注意:在界面被销毁时,调用headerCallBack的onDestroy方法,避免内存泄漏。* 118 | 119 | #### 代码混淆 120 | 121 | ``` 122 | -keep class net.arvin.itemdecorationhelper.**{*;} 123 | ``` 124 | 125 | ### TODO 126 | 127 | * 优化实现方式 128 | 129 | ### 原理 130 | 131 | 继承ItemDecoration,然后重写`getItemOffsets`和`onDraw`方法,实现分割线的绘制,通过重写`onDrawOver`实现粘性头部。 132 | 133 | 原理很简单,就是首先让itemView在你需要的方向偏移,例如left,top,right,botton;然后再在`onDraw`或者`onDrawOver`方法中获取到当前屏幕的view,并根据他们的位置,按照偏移的逻辑在相应区域绘制即可。借用一张图,感谢[【Android】RecyclerView:打造悬浮效果](https://www.jianshu.com/p/b335b620af39)。 134 | 135 | ![](https://upload-images.jianshu.io/upload_images/1638147-9e8a8158237c005c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/548) 136 | 137 | 很直观,可以看到不同内容绘制在不同层次,位置重合的话,上层会覆盖下层的内容。 138 | 139 | 具体的实现全是根据这个逻辑计算绘制。其中为了保持平分内容,将分割线拆分,如图:感谢[RecyclerView的 GridItemDecoration等分itemView](https://blog.csdn.net/qq_27192795/article/details/80563487) 140 | 141 | ![](https://upload-images.jianshu.io/upload_images/3157525-0c77a946eb7278c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 142 | 143 | 具体的难点就在这两个位置。 144 | 145 | 至于粘性头部,核心逻辑就是对数据的分组以及头部的偏移计算和绘制。也是万变不离其中。具体的实现请参考源码,含有一定的注释。 146 | 147 | ### 感谢 148 | 149 | [RecyclerView 悬浮/粘性头部——StickyHeaderDecoration](https://blog.csdn.net/qian520ao/article/details/76167193) 150 | 151 | [【Android】RecyclerView:打造悬浮效果](https://www.jianshu.com/p/b335b620af39) 152 | 153 | [RecyclerView的 GridItemDecoration等分itemView](https://blog.csdn.net/qq_27192795/article/details/80563487) 154 | 155 | 在实现过程中参考了很多文章,如果没有注明来源的,麻烦指出来,会直接加上引用来源的。 156 | 157 | ## License 158 | 159 | ``` 160 | Copyright 2018 arvinljw 161 | 162 | Licensed under the Apache License, Version 2.0 (the "License"); 163 | you may not use this file except in compliance with the License. 164 | You may obtain a copy of the License at 165 | 166 | http://www.apache.org/licenses/LICENSE-2.0 167 | 168 | Unless required by applicable law or agreed to in writing, software 169 | distributed under the License is distributed on an "AS IS" BASIS, 170 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 171 | See the License for the specific language governing permissions and 172 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/Demo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/Demo.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "net.arvin.itemdecorationhelper.sample" 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "2.0.0" 11 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'androidx.appcompat:appcompat:1.1.0' 24 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 25 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' 26 | implementation project(':itemdecorationhelper') 27 | // implementation 'com.github.arvinljw:ItemDecorationHelper:v1.0.5' 28 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"2.0.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/ContactAdapter.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.NonNull; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by arvinljw on 2018/7/9 18:51 15 | * Function: 16 | * Desc: 17 | */ 18 | public class ContactAdapter extends RecyclerView.Adapter { 19 | private Context context; 20 | private List items; 21 | 22 | ContactAdapter(Context context, List items) { 23 | this.context = context; 24 | this.items = items; 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 30 | return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_text, parent, false)); 31 | } 32 | 33 | @Override 34 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 35 | holder.tvName.setText(items.get(position).getName()); 36 | 37 | holder.itemView.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | public int getItemCount() { 46 | return items.size(); 47 | } 48 | 49 | static class ViewHolder extends RecyclerView.ViewHolder { 50 | TextView tvName; 51 | 52 | ViewHolder(View itemView) { 53 | super(itemView); 54 | tvName = itemView.findViewById(R.id.tv_text); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/ContactEntity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by arvinljw on 2018/7/25 13:31 8 | * Function: 9 | * Desc: 10 | */ 11 | public class ContactEntity implements Parcelable { 12 | private String name; 13 | private String pinyinName; 14 | private String letter; 15 | 16 | public ContactEntity(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getPinyinName() { 29 | return pinyinName; 30 | } 31 | 32 | public void setPinyinName(String pinyinName) { 33 | this.pinyinName = pinyinName; 34 | } 35 | 36 | public String getLetter() { 37 | return letter; 38 | } 39 | 40 | public void setLetter(String letter) { 41 | this.letter = letter; 42 | } 43 | 44 | @Override 45 | public int describeContents() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeString(this.name); 52 | dest.writeString(this.pinyinName); 53 | dest.writeString(this.letter); 54 | } 55 | 56 | protected ContactEntity(Parcel in) { 57 | this.name = in.readString(); 58 | this.pinyinName = in.readString(); 59 | this.letter = in.readString(); 60 | } 61 | 62 | public static final Creator CREATOR = new Creator() { 63 | @Override 64 | public ContactEntity createFromParcel(Parcel source) { 65 | return new ContactEntity(source); 66 | } 67 | 68 | @Override 69 | public ContactEntity[] newArray(int size) { 70 | return new ContactEntity[size]; 71 | } 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/GridActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import androidx.recyclerview.widget.GridLayoutManager; 4 | import androidx.recyclerview.widget.RecyclerView; 5 | 6 | /** 7 | * Created by arvinljw on 2018/7/25 14:03 8 | * Function: 9 | * Desc: 10 | */ 11 | public class GridActivity extends LinearActivity { 12 | 13 | @Override 14 | protected RecyclerView.LayoutManager getLayoutManager() { 15 | return new GridLayoutManager(this, 3, 16 | isVertical ? GridLayoutManager.VERTICAL : GridLayoutManager.HORIZONTAL, false); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/HanziToPinyin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 张涛. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.arvin.itemdecorationhelper.sample; 17 | 18 | import android.text.TextUtils; 19 | import android.util.Log; 20 | 21 | import java.text.Collator; 22 | import java.util.ArrayList; 23 | import java.util.Locale; 24 | 25 | /** 26 | * @author kymjs (http://www.kymjs.com/) on 9/17/15. 27 | */ 28 | public class HanziToPinyin { 29 | private static final String TAG = "HanziToPinyin"; 30 | 31 | // Turn on this flag when we want to check internal data structure. 32 | private static final boolean DEBUG = false; 33 | 34 | /** 35 | * Unihans array. 36 | *

37 | * Each unihans is the first one within same pinyin when collator is zh_CN. 38 | */ 39 | public static final char[] UNIHANS = { 40 | '\u963f', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b', 41 | '\u6300', '\u6273', '\u90a6', '\u52f9', '\u9642', '\u5954', 42 | '\u4f3b', '\u5c44', '\u8fb9', '\u706c', '\u618b', '\u6c43', 43 | '\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2', 44 | '\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u66fd', '\u66fe', 45 | '\u5c64', '\u53c9', '\u8286', '\u8fbf', '\u4f25', '\u6284', 46 | '\u8f66', '\u62bb', '\u6c88', '\u6c89', '\u9637', '\u5403', 47 | '\u5145', '\u62bd', '\u51fa', '\u6b3b', '\u63e3', '\u5ddb', 48 | '\u5205', '\u5439', '\u65fe', '\u9034', '\u5472', '\u5306', 49 | '\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413', 50 | '\u5491', '\u5446', '\u4e39', '\u5f53', '\u5200', '\u561a', 51 | '\u6265', '\u706f', '\u6c10', '\u55f2', '\u7538', '\u5201', 52 | '\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a', '\u53be', 53 | '\u8011', '\u8968', '\u5428', '\u591a', '\u59b8', '\u8bf6', 54 | '\u5940', '\u97a5', '\u513f', '\u53d1', '\u5e06', '\u531a', 55 | '\u98de', '\u5206', '\u4e30', '\u8985', '\u4ecf', '\u7d11', 56 | '\u4f15', '\u65ee', '\u4f85', '\u7518', '\u5188', '\u768b', 57 | '\u6208', '\u7ed9', '\u6839', '\u522f', '\u5de5', '\u52fe', 58 | '\u4f30', '\u74dc', '\u4e56', '\u5173', '\u5149', '\u5f52', 59 | '\u4e28', '\u5459', '\u54c8', '\u548d', '\u4f44', '\u592f', 60 | '\u8320', '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u5677', 61 | '\u53ff', '\u9f41', '\u4e6f', '\u82b1', '\u6000', '\u72bf', 62 | '\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0', 63 | '\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755', 64 | '\u5182', '\u4e29', '\u51e5', '\u59e2', '\u5658', '\u519b', 65 | '\u5494', '\u5f00', '\u520a', '\u5ffc', '\u5c3b', '\u533c', 66 | '\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938', 67 | '\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269', 68 | '\u5783', '\u6765', '\u5170', '\u5577', '\u635e', '\u808b', 69 | '\u52d2', '\u5d1a', '\u5215', '\u4fe9', '\u5941', '\u826f', 70 | '\u64a9', '\u5217', '\u62ce', '\u5222', '\u6e9c', '\u56d6', 71 | '\u9f99', '\u779c', '\u565c', '\u5a08', '\u7567', '\u62a1', 72 | '\u7f57', '\u5463', '\u5988', '\u57cb', '\u5ada', '\u7264', 73 | '\u732b', '\u4e48', '\u5445', '\u95e8', '\u753f', '\u54aa', 74 | '\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c', 75 | '\u6478', '\u54de', '\u6bea', '\u55ef', '\u62cf', '\u8149', 76 | '\u56e1', '\u56d4', '\u5b6c', '\u7592', '\u5a1e', '\u6041', 77 | '\u80fd', '\u59ae', '\u62c8', '\u5b22', '\u9e1f', '\u634f', 78 | '\u56dc', '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974', 79 | '\u597b', '\u759f', '\u9ec1', '\u90cd', '\u5594', '\u8bb4', 80 | '\u5991', '\u62cd', '\u7705', '\u4e53', '\u629b', '\u5478', 81 | '\u55b7', '\u5309', '\u4e15', '\u56e8', '\u527d', '\u6c15', 82 | '\u59d8', '\u4e52', '\u948b', '\u5256', '\u4ec6', '\u4e03', 83 | '\u6390', '\u5343', '\u545b', '\u6084', '\u767f', '\u4eb2', 84 | '\u72c5', '\u828e', '\u4e18', '\u533a', '\u5cd1', '\u7f3a', 85 | '\u590b', '\u5465', '\u7a63', '\u5a06', '\u60f9', '\u4eba', 86 | '\u6254', '\u65e5', '\u8338', '\u53b9', '\u909a', '\u633c', 87 | '\u5827', '\u5a51', '\u77a4', '\u637c', '\u4ee8', '\u6be2', 88 | '\u4e09', '\u6852', '\u63bb', '\u95aa', '\u68ee', '\u50e7', 89 | '\u6740', '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962', 90 | '\u7533', '\u8398', '\u6552', '\u5347', '\u5c38', '\u53ce', 91 | '\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01', 92 | '\u542e', '\u8bf4', '\u53b6', '\u5fea', '\u635c', '\u82cf', 93 | '\u72fb', '\u590a', '\u5b59', '\u5506', '\u4ed6', '\u56fc', 94 | '\u574d', '\u6c64', '\u5932', '\u5fd1', '\u71a5', '\u5254', 95 | '\u5929', '\u65eb', '\u5e16', '\u5385', '\u56f2', '\u5077', 96 | '\u51f8', '\u6e4d', '\u63a8', '\u541e', '\u4e47', '\u7a75', 97 | '\u6b6a', '\u5f2f', '\u5c23', '\u5371', '\u6637', '\u7fc1', 98 | '\u631d', '\u4e4c', '\u5915', '\u8672', '\u4eda', '\u4e61', 99 | '\u7071', '\u4e9b', '\u5fc3', '\u661f', '\u51f6', '\u4f11', 100 | '\u5401', '\u5405', '\u524a', '\u5743', '\u4e2b', '\u6079', 101 | '\u592e', '\u5e7a', '\u503b', '\u4e00', '\u56d9', '\u5e94', 102 | '\u54df', '\u4f63', '\u4f18', '\u625c', '\u56e6', '\u66f0', 103 | '\u6655', '\u7b60', '\u7b7c', '\u5e00', '\u707d', '\u5142', 104 | '\u5328', '\u50ae', '\u5219', '\u8d3c', '\u600e', '\u5897', 105 | '\u624e', '\u635a', '\u6cbe', '\u5f20', '\u957f', '\u9577', 106 | '\u4f4b', '\u8707', '\u8d1e', '\u4e89', '\u4e4b', '\u5cd9', 107 | '\u5ea2', '\u4e2d', '\u5dde', '\u6731', '\u6293', '\u62fd', 108 | '\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4e72', 109 | '\u5b97', '\u90b9', '\u79df', '\u94bb', '\u539c', '\u5c0a', 110 | '\u6628', '\u5159', '\u9fc3', '\u9fc4',}; 111 | 112 | /** 113 | * Pinyin array. 114 | *

115 | * Each pinyin is corresponding to unihans of same 116 | * offset in the unihans array. 117 | */ 118 | public static final byte[][] PINYINS = { 119 | {65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0}, 120 | {65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0}, 121 | {65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0}, 122 | {66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0}, 123 | {66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0}, 124 | {66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0}, 125 | {66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0}, 126 | {66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0}, 127 | {66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0}, 128 | {66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0}, 129 | {66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0}, 130 | {67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0}, 131 | {67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0}, 132 | {67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0}, 133 | {67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0}, 134 | {67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0}, 135 | {67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0}, 136 | {67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0}, 137 | {67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0}, 138 | {83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0}, 139 | {67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0}, 140 | {67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0}, 141 | {67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0}, 142 | {67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0}, 143 | {67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0}, 144 | {67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0}, 145 | {67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0}, 146 | {67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0}, 147 | {67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0}, 148 | {67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0}, 149 | {68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0}, 150 | {68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0}, 151 | {68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0}, 152 | {68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0}, 153 | {68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0}, 154 | {68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0}, 155 | {68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0}, 156 | {68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0}, 157 | {68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0}, 158 | {68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0}, 159 | {68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0}, 160 | {69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0}, 161 | {69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0}, 162 | {69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0}, 163 | {70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0}, 164 | {70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0}, 165 | {70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0}, 166 | {70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0}, 167 | {70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0}, 168 | {71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0}, 169 | {71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0}, 170 | {71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0}, 171 | {71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0}, 172 | {71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0}, 173 | {71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0}, 174 | {71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0}, 175 | {71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0}, 176 | {71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0}, 177 | {72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0}, 178 | {72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0}, 179 | {72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0}, 180 | {72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0}, 181 | {72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0}, 182 | {72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0}, 183 | {72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0}, 184 | {72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0}, 185 | {72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0}, 186 | {72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0}, 187 | {74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0}, 188 | {74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0}, 189 | {74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0}, 190 | {74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0}, 191 | {74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0}, 192 | {74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0}, 193 | {74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0}, 194 | {75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0}, 195 | {75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0}, 196 | {75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0}, 197 | {75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0}, 198 | {75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0}, 199 | {75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0}, 200 | {75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0}, 201 | {75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0}, 202 | {75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0}, 203 | {76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0}, 204 | {76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0}, 205 | {76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0}, 206 | {76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0}, 207 | {76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0}, 208 | {76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0}, 209 | {76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0}, 210 | {76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0}, 211 | {76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0}, 212 | {76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0}, 213 | {76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0}, 214 | {76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0}, 215 | {76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0}, 216 | {77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0}, 217 | {77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0}, 218 | {77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0}, 219 | {77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0}, 220 | {77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0}, 221 | {77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0}, 222 | {77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0}, 223 | {77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0}, 224 | {77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0}, 225 | {77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0}, 226 | {78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0}, 227 | {78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0}, 228 | {78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0}, 229 | {78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0}, 230 | {78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0}, 231 | {78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0}, 232 | {78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0}, 233 | {78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0}, 234 | {78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0}, 235 | {78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0}, 236 | {78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0}, 237 | {78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0}, 238 | {79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0}, 239 | {80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0}, 240 | {80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0}, 241 | {80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0}, 242 | {80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0}, 243 | {80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0}, 244 | {80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0}, 245 | {80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0}, 246 | {80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0}, 247 | {80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0}, 248 | {81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0}, 249 | {81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0}, 250 | {81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0}, 251 | {81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0}, 252 | {81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0}, 253 | {81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0}, 254 | {81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0}, 255 | {82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0}, 256 | {82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0}, 257 | {82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0}, 258 | {82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0}, 259 | {82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0}, 260 | {82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0}, 261 | {82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0}, 262 | {83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0}, 263 | {83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0}, 264 | {83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0}, 265 | {83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0}, 266 | {83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0}, 267 | {83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0}, 268 | {83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0}, 269 | {83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0}, 270 | {83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0}, 271 | {83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0}, 272 | {83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0}, 273 | {83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0}, 274 | {83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0}, 275 | {83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0}, 276 | {83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0}, 277 | {83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0}, 278 | {83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0}, 279 | {83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0}, 280 | {84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0}, 281 | {84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0}, 282 | {84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0}, 283 | {84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0}, 284 | {84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0}, 285 | {84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0}, 286 | {84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0}, 287 | {84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0}, 288 | {84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0}, 289 | {84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0}, 290 | {87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0}, 291 | {87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0}, 292 | {87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0}, 293 | {87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0}, 294 | {88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0}, 295 | {88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0}, 296 | {88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0}, 297 | {88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0}, 298 | {88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0}, 299 | {88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0}, 300 | {88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0}, 301 | {89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0}, 302 | {89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0}, 303 | {89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0}, 304 | {89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0}, 305 | {89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0}, 306 | {89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0}, 307 | {89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0}, 308 | {89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0}, 309 | {89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0}, 310 | {90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0}, 311 | {90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0}, 312 | {90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0}, 313 | {90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0}, 314 | {90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0}, 315 | {90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0}, 316 | {67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0}, 317 | {90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0}, 318 | {90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0}, 319 | {90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0}, 320 | {90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0}, 321 | {90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0}, 322 | {90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0}, 323 | {90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71}, 324 | {90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0}, 325 | {90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0}, 326 | {90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0}, 327 | {90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0}, 328 | {90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0}, 329 | {90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 330 | {83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0},}; 331 | 332 | /** 333 | * First and last Chinese character with known Pinyin according to zh collation 334 | */ 335 | private static final String FIRST_PINYIN_UNIHAN = "\u963F"; 336 | private static final String LAST_PINYIN_UNIHAN = "\u9FFF"; 337 | 338 | private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA); 339 | 340 | private static HanziToPinyin sInstance; 341 | private final boolean mHasChinaCollator; 342 | 343 | public static class Token { 344 | /** 345 | * Separator between target string for each source char 346 | */ 347 | public static final String SEPARATOR = " "; 348 | 349 | public static final int LATIN = 1; 350 | public static final int PINYIN = 2; 351 | public static final int UNKNOWN = 3; 352 | 353 | public Token() { 354 | } 355 | 356 | public Token(int type, String source, String target) { 357 | this.type = type; 358 | this.source = source; 359 | this.target = target; 360 | } 361 | 362 | /** 363 | * Type of this token, ASCII, PINYIN or UNKNOWN. 364 | */ 365 | public int type; 366 | /** 367 | * Original string before translation. 368 | */ 369 | public String source; 370 | /** 371 | * Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is 372 | * original string in source. 373 | */ 374 | public String target; 375 | } 376 | 377 | protected HanziToPinyin(boolean hasChinaCollator) { 378 | mHasChinaCollator = hasChinaCollator; 379 | } 380 | 381 | public static HanziToPinyin getInstance() { 382 | synchronized (HanziToPinyin.class) { 383 | if (sInstance != null) { 384 | return sInstance; 385 | } 386 | // Check if zh_CN collation data is available 387 | final Locale locale[] = Collator.getAvailableLocales(); 388 | for (int i = 0; i < locale.length; i++) { 389 | if (locale[i].equals(Locale.CHINESE)) { 390 | // Do self validation just once. 391 | if (DEBUG) { 392 | Log.d(TAG, "Self validation. Result: " + doSelfValidation()); 393 | } 394 | sInstance = new HanziToPinyin(true); 395 | return sInstance; 396 | } 397 | } 398 | Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled"); 399 | sInstance = new HanziToPinyin(false); 400 | return sInstance; 401 | } 402 | } 403 | 404 | /** 405 | * Validate if our internal table has some wrong value. 406 | * 407 | * @return true when the table looks correct. 408 | */ 409 | private static boolean doSelfValidation() { 410 | char lastChar = UNIHANS[0]; 411 | String lastString = Character.toString(lastChar); 412 | for (char c : UNIHANS) { 413 | if (lastChar == c) { 414 | continue; 415 | } 416 | final String curString = Character.toString(c); 417 | int cmp = COLLATOR.compare(lastString, curString); 418 | if (cmp >= 0) { 419 | Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString 420 | + "\" is greater than current string \"" + curString + "\"."); 421 | return false; 422 | } 423 | lastString = curString; 424 | } 425 | return true; 426 | } 427 | 428 | private Token getToken(char character) { 429 | Token token = new Token(); 430 | final String letter = Character.toString(character); 431 | token.source = letter; 432 | int offset = -1; 433 | int cmp; 434 | if (character < 256) { 435 | token.type = Token.LATIN; 436 | token.target = letter; 437 | return token; 438 | } else { 439 | cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN); 440 | if (cmp < 0) { 441 | token.type = Token.UNKNOWN; 442 | token.target = letter; 443 | return token; 444 | } else if (cmp == 0) { 445 | token.type = Token.PINYIN; 446 | offset = 0; 447 | } else { 448 | cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN); 449 | if (cmp > 0) { 450 | token.type = Token.UNKNOWN; 451 | token.target = letter; 452 | return token; 453 | } else if (cmp == 0) { 454 | token.type = Token.PINYIN; 455 | offset = UNIHANS.length - 1; 456 | } 457 | } 458 | } 459 | 460 | token.type = Token.PINYIN; 461 | if (offset < 0) { 462 | int begin = 0; 463 | int end = UNIHANS.length - 1; 464 | while (begin <= end) { 465 | offset = (begin + end) / 2; 466 | final String unihan = Character.toString(UNIHANS[offset]); 467 | cmp = COLLATOR.compare(letter, unihan); 468 | if (cmp == 0) { 469 | break; 470 | } else if (cmp > 0) { 471 | begin = offset + 1; 472 | } else { 473 | end = offset - 1; 474 | } 475 | } 476 | } 477 | if (cmp < 0) { 478 | offset--; 479 | } 480 | StringBuilder pinyin = new StringBuilder(); 481 | for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) { 482 | pinyin.append((char) PINYINS[offset][j]); 483 | } 484 | token.target = pinyin.toString(); 485 | if (TextUtils.isEmpty(token.target)) { 486 | token.type = Token.UNKNOWN; 487 | token.target = token.source; 488 | } 489 | return token; 490 | } 491 | 492 | /** 493 | * Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without 494 | * space will be put into a Token, One Hanzi character which has pinyin will be treated as a 495 | * Token. If these is no China collator, the empty token array is returned. 496 | */ 497 | public ArrayList get(final String input) { 498 | ArrayList tokens = new ArrayList(); 499 | if (!mHasChinaCollator || TextUtils.isEmpty(input)) { 500 | // return empty tokens. 501 | return tokens; 502 | } 503 | final int inputLength = input.length(); 504 | final StringBuilder sb = new StringBuilder(); 505 | int tokenType = Token.LATIN; 506 | // Go through the input, create a new token when 507 | // a. Token type changed 508 | // b. Get the Pinyin of current charater. 509 | // c. current character is space. 510 | for (int i = 0; i < inputLength; i++) { 511 | final char character = input.charAt(i); 512 | if (character == ' ') { 513 | if (sb.length() > 0) { 514 | addToken(sb, tokens, tokenType); 515 | } 516 | } else if (character < 256) { 517 | if (tokenType != Token.LATIN && sb.length() > 0) { 518 | addToken(sb, tokens, tokenType); 519 | } 520 | tokenType = Token.LATIN; 521 | sb.append(character); 522 | } else { 523 | Token t = getToken(character); 524 | if (t.type == Token.PINYIN) { 525 | if (sb.length() > 0) { 526 | addToken(sb, tokens, tokenType); 527 | } 528 | tokens.add(t); 529 | tokenType = Token.PINYIN; 530 | } else { 531 | if (tokenType != t.type && sb.length() > 0) { 532 | addToken(sb, tokens, tokenType); 533 | } 534 | tokenType = t.type; 535 | sb.append(character); 536 | } 537 | } 538 | } 539 | if (sb.length() > 0) { 540 | addToken(sb, tokens, tokenType); 541 | } 542 | return tokens; 543 | } 544 | 545 | private void addToken( 546 | final StringBuilder sb, final ArrayList tokens, final int tokenType) { 547 | String str = sb.toString(); 548 | tokens.add(new Token(tokenType, str, str)); 549 | sb.setLength(0); 550 | } 551 | 552 | /** 553 | * 汉字转换拼音,字母原样返回,都转换为小写 554 | * 555 | * @param input 556 | * @return 557 | */ 558 | public static String getPinYin(String input) { 559 | ArrayList tokens = HanziToPinyin.getInstance().get(input); 560 | StringBuilder sb = new StringBuilder(); 561 | if (tokens != null && tokens.size() > 0) { 562 | for (Token token : tokens) { 563 | if (token.type == Token.PINYIN) { 564 | sb.append(token.target); 565 | } else { 566 | sb.append(token.source); 567 | } 568 | } 569 | } 570 | return sb.toString().trim().toLowerCase(); 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/LinearActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.util.SparseArray; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.chad.library.adapter.base.BaseQuickAdapter; 15 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 16 | 17 | import net.arvin.itemdecorationhelper.BaseStickyDividerItemDecoration; 18 | import net.arvin.itemdecorationhelper.DefaultHeaderCallBack; 19 | import net.arvin.itemdecorationhelper.GroupData; 20 | import net.arvin.itemdecorationhelper.ItemDecorationFactory; 21 | import net.arvin.itemdecorationhelper.OnHeaderClickListener; 22 | 23 | import java.util.ArrayList; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | /** 29 | * Created by arvinljw on 2018/7/25 10:27 30 | * Function: 31 | * Desc: 32 | */ 33 | public class LinearActivity extends AppCompatActivity { 34 | protected boolean isVertical; 35 | protected boolean havingHeader; 36 | 37 | RecyclerView recyclerView; 38 | protected List items = new ArrayList<>(); 39 | private Map index = new HashMap<>();//字母对应在items中的位置 40 | private List keys = new ArrayList<>();//字母列表 41 | private SparseArray dataMap = new SparseArray<>();//每一项对应的分组信息 42 | private RecyclerView.ItemDecoration itemDecoration; 43 | private DefaultHeaderCallBack headerCallBack; 44 | 45 | @Override 46 | protected void onCreate(@Nullable Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | setContentView(R.layout.activity_linear); 49 | 50 | isVertical = getIntent().getBooleanExtra(MainActivity.ORIENTATION, false); 51 | havingHeader = getIntent().getBooleanExtra(MainActivity.HAVING_HEADER, false); 52 | recyclerView = findViewById(R.id.recycler_view); 53 | recyclerView.setLayoutManager(getLayoutManager()); 54 | 55 | items.addAll(getData()); 56 | if (havingHeader) { 57 | groupInfo(); 58 | } 59 | final BaseQuickAdapter adapter = getAdapter(); 60 | recyclerView.setAdapter(adapter); 61 | 62 | itemDecoration = getItemDecoration(); 63 | recyclerView.addItemDecoration(itemDecoration); 64 | 65 | } 66 | 67 | protected RecyclerView.LayoutManager getLayoutManager() { 68 | return new LinearLayoutManager(this, 69 | isVertical ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL, false); 70 | } 71 | 72 | private RecyclerView.ItemDecoration getItemDecoration() { 73 | RecyclerView.ItemDecoration itemDecoration; 74 | if (havingHeader) { 75 | headerCallBack = new DefaultHeaderCallBack(this) { 76 | @Override 77 | public GroupData getGroupData(int position) { 78 | if (items.size() == 0) { 79 | return null; 80 | } 81 | GroupData data = dataMap.get(position); 82 | if (data == null) { 83 | String letter = items.get(position).getLetter(); 84 | int i = keys.indexOf(letter); 85 | int groupLength;//通过下一个字母的位置减去当前字母的位置,如果是最后一个就使用总共的数量减去当前字母的位置 86 | if (i < keys.size() - 1) { 87 | String str = keys.get(i + 1); 88 | groupLength = index.get(str) - index.get(letter); 89 | } else { 90 | groupLength = items.size() - index.get(letter); 91 | } 92 | data = new GroupData(letter) 93 | .position(position - index.get(letter))//在分组中的位置,就用实际位置减去当前字母的位置 94 | .groupLength(groupLength); 95 | } 96 | return data; 97 | } 98 | }; 99 | itemDecoration = new ItemDecorationFactory.StickyDividerBuilder() 100 | .dividerHeight(2) 101 | .dividerColor(Color.parseColor("#D8D8D8")) 102 | .callback(headerCallBack) 103 | .build(recyclerView); 104 | ((BaseStickyDividerItemDecoration) itemDecoration).setOnHeaderClickListener(new OnHeaderClickListener() { 105 | @Override 106 | public void onHeaderClicked(int position) { 107 | Log.d("headerClicked", items.get(position).getLetter()); 108 | } 109 | }); 110 | } else { 111 | itemDecoration = new ItemDecorationFactory.DividerBuilder() 112 | .dividerHeight(2) 113 | .dividerColor(Color.parseColor("#D8D8D8")) 114 | .showLastDivider(false)//默认是true 115 | .build(recyclerView); 116 | } 117 | return itemDecoration; 118 | } 119 | 120 | protected BaseQuickAdapter getAdapter() { 121 | return new BaseQuickAdapter(R.layout.item_text, items) { 122 | @Override 123 | protected void convert(BaseViewHolder helper, ContactEntity item) { 124 | ViewGroup.LayoutParams layoutParams = helper.itemView.getLayoutParams(); 125 | if (!isVertical) { 126 | layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 127 | helper.itemView.setLayoutParams(layoutParams); 128 | } 129 | helper.itemView.setOnClickListener(new View.OnClickListener() { 130 | @Override 131 | public void onClick(View v) { 132 | Log.d("height", v.getHeight() + ""); 133 | } 134 | }); 135 | helper.setText(R.id.tv_text, item.getName()); 136 | } 137 | }; 138 | } 139 | 140 | protected List getData() { 141 | return TestData.getData(); 142 | } 143 | 144 | private void groupInfo() { 145 | index = new HashMap<>(); 146 | keys = new ArrayList<>(); 147 | for (int i = 0; i < items.size(); i++) { 148 | String ch = items.get(i).getLetter(); 149 | if (!index.containsKey(ch)) { 150 | index.put(ch, i); 151 | keys.add(ch); 152 | } 153 | } 154 | } 155 | 156 | @Override 157 | protected void onDestroy() { 158 | super.onDestroy(); 159 | if (headerCallBack != null) { 160 | headerCallBack.onDestroy(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import android.content.Intent; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import androidx.appcompat.widget.AppCompatRadioButton; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 11 | public static final String ORIENTATION = "orientation"; 12 | public static final String HAVING_HEADER = "having_header"; 13 | 14 | AppCompatRadioButton rbVertical; 15 | AppCompatRadioButton rbHeaderHaving; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | rbVertical = findViewById(R.id.rb_vertical); 22 | rbHeaderHaving = findViewById(R.id.rb_having); 23 | initEvent(); 24 | } 25 | 26 | private void initEvent() { 27 | findViewById(R.id.btn_linear).setOnClickListener(this); 28 | findViewById(R.id.btn_grid).setOnClickListener(this); 29 | findViewById(R.id.btn_staggered).setOnClickListener(this); 30 | } 31 | 32 | 33 | @Override 34 | public void onClick(View v) { 35 | switch (v.getId()) { 36 | case R.id.btn_linear: 37 | toLinear(); 38 | break; 39 | case R.id.btn_grid: 40 | toGrid(); 41 | break; 42 | case R.id.btn_staggered: 43 | toStaggeredGrid(); 44 | break; 45 | } 46 | } 47 | 48 | private void toLinear() { 49 | boolean isVertical = rbVertical.isChecked(); 50 | boolean havingHeader = rbHeaderHaving.isChecked(); 51 | Intent intent = new Intent(this, LinearActivity.class); 52 | intent.putExtra(ORIENTATION, isVertical); 53 | intent.putExtra(HAVING_HEADER, havingHeader); 54 | startActivity(intent); 55 | } 56 | 57 | private void toGrid() { 58 | boolean isVertical = rbVertical.isChecked(); 59 | boolean havingHeader = rbHeaderHaving.isChecked(); 60 | if (havingHeader && !isVertical) { 61 | Toast.makeText(this, "GridLayoutManager暂不支持水平方向带头部的布局", Toast.LENGTH_LONG).show(); 62 | return; 63 | } 64 | Intent intent = new Intent(this, GridActivity.class); 65 | intent.putExtra(ORIENTATION, isVertical); 66 | intent.putExtra(HAVING_HEADER, havingHeader); 67 | startActivity(intent); 68 | } 69 | 70 | private void toStaggeredGrid() { 71 | boolean isVertical = rbVertical.isChecked(); 72 | boolean havingHeader = rbHeaderHaving.isChecked(); 73 | if (havingHeader) { 74 | Toast.makeText(this, "StaggeredGridLayoutManager暂不支持带头部的布局", Toast.LENGTH_LONG).show(); 75 | return; 76 | } 77 | Intent intent = new Intent(this, StaggeredGridActivity.class); 78 | intent.putExtra(ORIENTATION, isVertical); 79 | startActivity(intent); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/StaggeredGridActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager; 4 | import androidx.recyclerview.widget.RecyclerView; 5 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.chad.library.adapter.base.BaseQuickAdapter; 11 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Random; 16 | 17 | /** 18 | * Created by arvinljw on 2018/7/25 14:03 19 | * Function: 20 | * Desc: 21 | */ 22 | public class StaggeredGridActivity extends LinearActivity { 23 | @Override 24 | protected RecyclerView.LayoutManager getLayoutManager() { 25 | return new StaggeredGridLayoutManager(4, isVertical ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL); 26 | } 27 | 28 | @Override 29 | protected BaseQuickAdapter getAdapter() { 30 | return new BaseQuickAdapter(R.layout.item_text, items) { 31 | @Override 32 | protected void convert(BaseViewHolder helper, ContactEntity item) { 33 | ViewGroup.LayoutParams layoutParams = helper.itemView.getLayoutParams(); 34 | if (!isVertical) { 35 | layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 36 | } 37 | layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; 38 | helper.itemView.setLayoutParams(layoutParams); 39 | helper.itemView.setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | Log.d("height", v.getHeight() + ""); 43 | } 44 | }); 45 | helper.setText(R.id.tv_text, item.getName()); 46 | } 47 | }; 48 | } 49 | 50 | @Override 51 | protected List getData() { 52 | List data = new ArrayList<>(); 53 | Random random = new Random(47); 54 | for (int i = 0; i < 123; i++) { 55 | int column = i % 4 + 1; 56 | if (column == 1) { 57 | data.add(new ContactEntity("item->" + (i + 1) + "--float--" + random.nextFloat())); 58 | } else if (column == 3) { 59 | data.add(new ContactEntity("item->" + (i + 1) + "--int--" + random.nextInt())); 60 | } else { 61 | data.add(new ContactEntity("item->" + (i + 1))); 62 | } 63 | } 64 | return data; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/itemdecorationhelper/sample/TestData.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper.sample; 2 | 3 | import java.text.Collator; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.Comparator; 8 | import java.util.List; 9 | import java.util.Locale; 10 | 11 | /** 12 | * Created by arvinljw on 2018/7/25 11:15 13 | * Function: 14 | * Desc: 15 | */ 16 | public class TestData { 17 | public static List getData() { 18 | List names = new ArrayList<>(Arrays.asList("李渊", "李世民", "侯君集", "李靖", "魏征", "房玄龄", "杜如晦", "柴绍", "程知节", "尉迟恭", "秦琼", 19 | "长孙无忌", "李存恭", "封德彝", "段志玄", "刘弘基", "徐世绩", "李治", "武则天", "太平公主", "韦后", "李隆基", "杨玉环", "王勃", 20 | "陈子昂", "卢照邻", "杨炯", "王之涣", "安禄山", "史思明", "张巡", "雷万春", "李白", "高力士", "杜甫", "白居易", "王维", "孟浩然", 21 | "杜牧", "李商隐", "郭子仪", "张易之", "张昌宗", "来俊臣", "杨国忠", "李林甫", "高适", "王昌龄", "孙思邈", "玄奘", "鉴真", "高骈", 22 | "狄仁杰", "黄巢", "王仙芝", "文成公主", "松赞干布", "薛涛", "鱼玄机", "贺知章", "李泌", "韩愈", "柳宗元", "上官婉儿", "朱温", 23 | "刘仁恭", "丁会", "李克用", "李存勖", "葛从周", "王建", "刘知远", "石敬瑭", "郭威", "柴荣", "孟昶", "荆浩", "刘彟", "张及之", "杜宇", 24 | "高季兴", "喻皓", "历真", "李茂贞", "朱友珪", "朱友贞", "刘守光", "卢文进", "李嗣源", "冯行袭", "康义诚", "薛贻矩", "朱弘昭", "冯赟", 25 | "李存孝", "霍存", "张归霸", "张延寿", "氏叔琮", "朱瑾", "朱珍", "张存敬", "牛存节", "李罕之", "乐从训", "王师范", "康怀英", "王彦章", 26 | "时溥", "秦宗权", "史懿", "苏逢吉", "杨邡", "桑维汉", "耶律德光", "安重荣", "边光范", "袁继忠", "李筠", "薛怀让", "赵匡胤", "赵匡义", 27 | "石守信", "慕容延钊", "曹彬", "潘美", "赵普", "杨业", "田重进", "王禹偁", "林逋", "杨延昭", "杨文广", "包拯", "狄青", "寇准", "范仲淹", 28 | "司马光", "欧阳修", "苏轼", "苏辙", "王安石", "吕惠卿", "苏洵", "宋江", "方腊", "岳飞", "秦桧", "韩世忠", "梁红玉", 29 | "赵构", "朱熹", "柳永", "黄庭坚", "秦观", "晏殊", "晏几道", "陆游", "辛弃疾", "魏良臣", "李清照", "唐婉", "史弥远", "韩佗胄", 30 | "贾似道", "丁大全", "文天祥", "陆秀夫", "高俅", "蔡京", "杨戬", "童贯", "张叔夜", "韩锜", "岳云", "张宪", "梅尧臣", "苏舜钦", 31 | "吕文焕", "吕文德", "杨幺")); 32 | List contacts = new ArrayList<>(); 33 | for (String name : names) { 34 | ContactEntity entity = new ContactEntity(name); 35 | entity.setPinyinName(HanziToPinyin.getPinYin(name)); 36 | entity.setLetter(getLetter(entity.getPinyinName())); 37 | contacts.add(entity); 38 | } 39 | Collections.sort(contacts, new NameComparator()); 40 | return contacts; 41 | } 42 | 43 | private static String getLetter(String pinyinName) { 44 | char ch = pinyinName.charAt(0); 45 | if (ch < 'a' || ch > 'z') { 46 | ch = '#'; 47 | } 48 | return String.valueOf(ch).toUpperCase(); 49 | } 50 | 51 | public static class NameComparator implements Comparator { 52 | 53 | @Override 54 | public int compare(ContactEntity o1, ContactEntity o2) { 55 | Comparator cmp = Collator.getInstance(Locale.CHINA); 56 | String[] str = new String[2]; 57 | str[0] = o1.getName(); 58 | str[1] = o2.getName(); 59 | Arrays.sort(str, cmp); 60 | if (str[0].equals(str[1])) { 61 | return 0; 62 | } 63 | if (str[0].equals(o1.getName())) { 64 | return -1; 65 | } else { 66 | return 1; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/bg_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_linear.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 44 | 45 | 50 | 51 | 58 | 59 | 60 | 61 | 71 | 72 | 82 | 83 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_section_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48dp 4 | 80dp 5 | 300dp 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ItemDecorationHelper 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.4.1' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url "https://jitpack.io" } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ItemDecorationHelper/76a68ae523599430f829f5be60ea91bc0593a356/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 13 21:34:51 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /itemdecorationhelper/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /itemdecorationhelper/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | group='com.github.arvinljw' 4 | 5 | android { 6 | compileSdkVersion 28 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "2.0.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | // build a jar with source files 22 | task sourcesJar(type: Jar) { 23 | from android.sourceSets.main.java.srcDirs 24 | classifier = 'sources' 25 | } 26 | task javadoc(type: Javadoc) { 27 | failOnError false 28 | source = android.sourceSets.main.java.sourceFiles 29 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 30 | classpath += configurations.compile 31 | } 32 | // build a jar with javadoc 33 | task javadocJar(type: Jar, dependsOn: javadoc) { 34 | classifier = 'javadoc' 35 | from javadoc.destinationDir 36 | } 37 | artifacts { 38 | archives sourcesJar 39 | archives javadocJar 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: 'libs', include: ['*.jar']) 45 | compileOnly 'androidx.recyclerview:recyclerview:1.1.0' 46 | } 47 | -------------------------------------------------------------------------------- /itemdecorationhelper/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/BaseDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import androidx.recyclerview.widget.RecyclerView; 4 | 5 | /** 6 | * Created by arvinljw on 2018/7/24 09:42 7 | * Function: 8 | * Desc: 9 | */ 10 | public class BaseDividerItemDecoration extends RecyclerView.ItemDecoration { 11 | protected ItemDecorationHelper.DividerHelper dividerHelper; 12 | 13 | BaseDividerItemDecoration(ItemDecorationFactory.DividerBuilder builder) { 14 | dividerHelper = new ItemDecorationHelper.DividerHelper(builder); 15 | } 16 | 17 | public ItemDecorationHelper.DividerHelper getDividerHelper() { 18 | return dividerHelper; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/BaseStickyDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import androidx.recyclerview.widget.RecyclerView; 5 | import android.util.SparseIntArray; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by arvinljw on 2018/7/24 15:17 10 | * Function: 11 | * Desc: 12 | */ 13 | public class BaseStickyDividerItemDecoration extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener { 14 | protected ItemDecorationHelper.StickyDividerHelper stickyDividerHelper; 15 | protected SparseIntArray headersTop; 16 | private OnHeaderClickListener headerClickListener; 17 | private StickyHeaderClickGestureDetector gestureDetector; 18 | 19 | BaseStickyDividerItemDecoration(ItemDecorationFactory.StickyDividerBuilder builder) { 20 | stickyDividerHelper = new ItemDecorationHelper.StickyDividerHelper(builder); 21 | headersTop = new SparseIntArray(); 22 | } 23 | 24 | @Override 25 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 26 | super.onDrawOver(c, parent, state); 27 | headersTop.clear(); 28 | if (gestureDetector == null) { 29 | parent.addOnItemTouchListener(this); 30 | gestureDetector = new StickyHeaderClickGestureDetector(parent.getContext(), headersTop, 31 | stickyDividerHelper); 32 | } 33 | gestureDetector.setOnHeaderClickListener(headerClickListener); 34 | } 35 | 36 | public void setOnHeaderClickListener(OnHeaderClickListener headerClickListener) { 37 | this.headerClickListener = headerClickListener; 38 | } 39 | 40 | public ItemDecorationHelper.StickyDividerHelper getStickyDividerHelper() { 41 | return stickyDividerHelper; 42 | } 43 | 44 | @Override 45 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { 46 | return gestureDetector.onTouchEvent(e); 47 | } 48 | 49 | @Override 50 | public void onTouchEvent(RecyclerView rv, MotionEvent e) { 51 | } 52 | 53 | @Override 54 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/DefaultHeaderCallBack.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import java.util.WeakHashMap; 9 | 10 | /** 11 | * Created by arvinljw on 2018/7/27 14:13 12 | * Function: 13 | * Desc: 14 | */ 15 | public abstract class DefaultHeaderCallBack implements StickyDividerCallback { 16 | private Context context; 17 | private WeakHashMap headerMap; 18 | 19 | public DefaultHeaderCallBack(Context context) { 20 | this.context = context; 21 | headerMap = new WeakHashMap<>(); 22 | } 23 | 24 | @Override 25 | public View getStickyHeaderView(int position) { 26 | View headerView = headerMap.get(position); 27 | if (headerView == null) { 28 | headerView = LayoutInflater.from(context).inflate(R.layout.item_decoration_default_header, null); 29 | TextView tvHeader = (TextView) headerView.findViewById(R.id.tv_header); 30 | GroupData groupData = getGroupData(position); 31 | if (groupData != null) { 32 | tvHeader.setText(groupData.getTitle()); 33 | } 34 | headerMap.put(position, headerView); 35 | } 36 | return headerView; 37 | } 38 | 39 | public void onDestroy() { 40 | context = null; 41 | headerMap.clear(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/GridDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by arvinljw on 2018/7/23 09:39 10 | * Function: 11 | * Desc:GridLayoutManager对应的分割线 12 | */ 13 | public class GridDividerItemDecoration extends BaseDividerItemDecoration { 14 | GridDividerItemDecoration(ItemDecorationFactory.DividerBuilder builder) { 15 | super(builder); 16 | } 17 | 18 | @Override 19 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 20 | super.getItemOffsets(outRect, view, parent, state); 21 | ItemDecorationHelper.getGridItemOffset(outRect, parent, view, dividerHelper); 22 | } 23 | 24 | @Override 25 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 26 | super.onDraw(c, parent, state); 27 | ItemDecorationHelper.onGridDraw(c, parent, dividerHelper); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/GroupData.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | /** 4 | * Created by arvinljw on 2018/7/24 15:30 5 | * Function: 6 | * Desc: 7 | */ 8 | public class GroupData { 9 | private String title; 10 | //在分组内的位置 11 | private int position; 12 | // 组的成员个数 13 | private int groupLength; 14 | 15 | public GroupData(String title) { 16 | this.title = title; 17 | } 18 | 19 | public GroupData title(String title) { 20 | this.title = title; 21 | return this; 22 | } 23 | 24 | public GroupData position(int position) { 25 | this.position = position; 26 | return this; 27 | } 28 | 29 | public GroupData groupLength(int groupLength) { 30 | this.groupLength = groupLength; 31 | return this; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public int getPosition() { 39 | return position; 40 | } 41 | 42 | public int getGroupLength() { 43 | return groupLength; 44 | } 45 | 46 | public boolean isFirstViewInGroup() { 47 | return position == 0; 48 | } 49 | 50 | public boolean isLastViewInGroup() { 51 | return position == groupLength - 1 && position >= 0; 52 | } 53 | 54 | public boolean isFirstLineInGroup(int spanCount) { 55 | return position < spanCount; 56 | } 57 | 58 | public boolean isLastLineInGroup(int spanCount) { 59 | return position / spanCount + 1 == groupLength / spanCount + (groupLength % spanCount == 0 ? 0 : 1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/ItemDecorationFactory.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Color; 4 | import androidx.annotation.ColorInt; 5 | import androidx.recyclerview.widget.GridLayoutManager; 6 | import androidx.recyclerview.widget.LinearLayoutManager; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 9 | 10 | /** 11 | * Created by arvinljw on 2018/7/24 09:36 12 | * Function: 13 | * Desc: 14 | */ 15 | public class ItemDecorationFactory { 16 | private static final int DEFAULT_DIVIDER_HEIGHT = 2; 17 | private static final int DEFAULT_DIVIDER_COLOR = Color.parseColor("#D8D8D8"); 18 | private static final boolean DEFAULT_SHOW_LAST_DIVIDER = true; 19 | 20 | public static class DividerBuilder { 21 | private int dividerHeight; 22 | private int dividerColor; 23 | private boolean showLastDivider; 24 | 25 | public DividerBuilder() { 26 | dividerHeight = DEFAULT_DIVIDER_HEIGHT; 27 | dividerColor = DEFAULT_DIVIDER_COLOR; 28 | showLastDivider = DEFAULT_SHOW_LAST_DIVIDER; 29 | } 30 | 31 | public DividerBuilder dividerHeight(int dividerHeight) { 32 | this.dividerHeight = dividerHeight; 33 | return this; 34 | } 35 | 36 | public DividerBuilder dividerColor(@ColorInt int dividerColor) { 37 | this.dividerColor = dividerColor; 38 | return this; 39 | } 40 | 41 | /** 42 | * 只用于LinearLayoutManager,处理最后一项是否显示分割线 43 | */ 44 | public DividerBuilder showLastDivider(boolean showLastDivider) { 45 | this.showLastDivider = showLastDivider; 46 | return this; 47 | } 48 | 49 | public int getDividerHeight() { 50 | return dividerHeight; 51 | } 52 | 53 | public int getDividerColor() { 54 | return dividerColor; 55 | } 56 | 57 | public boolean isShowLastDivider() { 58 | return showLastDivider; 59 | } 60 | 61 | public RecyclerView.ItemDecoration build(RecyclerView recyclerView) { 62 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 63 | if (layoutManager instanceof GridLayoutManager) { 64 | return new GridDividerItemDecoration(this); 65 | } 66 | if (layoutManager instanceof LinearLayoutManager) { 67 | return new LinearDividerItemDecoration(this); 68 | } 69 | if (layoutManager instanceof StaggeredGridLayoutManager) { 70 | return new StaggeredGridDividerItemDecoration(this); 71 | } 72 | return null; 73 | } 74 | } 75 | 76 | public static class StickyDividerBuilder { 77 | private int dividerHeight; 78 | private int dividerColor; 79 | private boolean showLastDivider; 80 | 81 | private StickyDividerCallback callback; 82 | 83 | public StickyDividerBuilder() { 84 | dividerHeight = DEFAULT_DIVIDER_HEIGHT; 85 | dividerColor = DEFAULT_DIVIDER_COLOR; 86 | showLastDivider = DEFAULT_SHOW_LAST_DIVIDER; 87 | } 88 | 89 | public StickyDividerBuilder callback(StickyDividerCallback callback) { 90 | this.callback = callback; 91 | return this; 92 | } 93 | 94 | public StickyDividerBuilder dividerHeight(int dividerHeight) { 95 | this.dividerHeight = dividerHeight; 96 | return this; 97 | } 98 | 99 | public StickyDividerBuilder dividerColor(@ColorInt int dividerColor) { 100 | this.dividerColor = dividerColor; 101 | return this; 102 | } 103 | 104 | /** 105 | * 只用于LinearLayoutManager,处理最后一项是否显示分割线 106 | */ 107 | public StickyDividerBuilder showLastDivider(boolean showLastDivider) { 108 | this.showLastDivider = showLastDivider; 109 | return this; 110 | } 111 | 112 | public int getDividerHeight() { 113 | return dividerHeight; 114 | } 115 | 116 | public int getDividerColor() { 117 | return dividerColor; 118 | } 119 | 120 | public boolean isShowLastDivider() { 121 | return showLastDivider; 122 | } 123 | 124 | public StickyDividerCallback getCallback() { 125 | return callback; 126 | } 127 | 128 | public BaseStickyDividerItemDecoration build(RecyclerView recyclerView) { 129 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 130 | if (layoutManager instanceof GridLayoutManager) { 131 | return new StickyGridDividerItemDecoration(this); 132 | } 133 | if (layoutManager instanceof LinearLayoutManager) { 134 | return new StickyLinearDividerItemDecoration(this); 135 | } 136 | return null; 137 | } 138 | } 139 | 140 | 141 | } 142 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/ItemDecorationHelper.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Rect; 6 | import androidx.recyclerview.widget.GridLayoutManager; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 10 | import android.util.SparseIntArray; 11 | import android.view.View; 12 | 13 | /** 14 | * Created by arvinljw on 2018/7/24 14:48 15 | * Function: 16 | * Desc: 17 | */ 18 | public class ItemDecorationHelper { 19 | 20 | //--LinearItemDecoration -- start 21 | public static void getLinearItemOffset(Rect outRect, View view, RecyclerView parent, IDivider dividerHelper) { 22 | if (!(parent.getLayoutManager() instanceof LinearLayoutManager)) { 23 | return; 24 | } 25 | LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); 26 | 27 | if (layoutManager.getOrientation() == LinearLayoutManager.VERTICAL) { 28 | outRect.set(0, 0, 0, dividerHelper.getDividerHeight()); 29 | if (dontShowLastDivider(view, parent, dividerHelper)) { 30 | outRect.bottom = 0; 31 | } 32 | } else { 33 | //noinspection SuspiciousNameCombination 34 | outRect.set(0, 0, dividerHelper.getDividerHeight(), 0); 35 | if (dontShowLastDivider(view, parent, dividerHelper)) { 36 | outRect.right = 0; 37 | } 38 | } 39 | } 40 | 41 | public static void onLinearDraw(Canvas c, RecyclerView parent, IDivider itemDecoration) { 42 | if (!(parent.getLayoutManager() instanceof LinearLayoutManager)) { 43 | return; 44 | } 45 | LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); 46 | if (layoutManager.getOrientation() == LinearLayoutManager.VERTICAL) { 47 | drawLinearVertical(c, parent, itemDecoration); 48 | } else { 49 | drawLinearHorizontal(c, parent, itemDecoration); 50 | } 51 | } 52 | 53 | private static void drawLinearVertical(Canvas c, RecyclerView parent, IDivider dividerHelper) { 54 | int left, top, right, bottom; 55 | int childCount = parent.getChildCount(); 56 | for (int i = 0; i < childCount; i++) { 57 | View child = parent.getChildAt(i); 58 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 59 | left = parent.getLeft(); 60 | top = child.getBottom() + layoutParams.bottomMargin; 61 | right = parent.getRight(); 62 | bottom = top + dividerHelper.getDividerHeight(); 63 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 64 | } 65 | } 66 | 67 | private static void drawLinearHorizontal(Canvas c, RecyclerView parent, IDivider dividerHelper) { 68 | int left, top, right, bottom; 69 | int childCount = parent.getChildCount(); 70 | for (int i = 0; i < childCount; i++) { 71 | View child = parent.getChildAt(i); 72 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 73 | left = child.getRight() + layoutParams.rightMargin; 74 | top = child.getTop(); 75 | right = left + dividerHelper.getDividerHeight(); 76 | bottom = child.getBottom(); 77 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 78 | } 79 | } 80 | //--LinearDividerItemDecoration -- end 81 | 82 | //--GridDividerItemDecoration -- start 83 | public static void getGridItemOffset(Rect outRect, RecyclerView parent, View view, IDivider dividerHelper) { 84 | if (!(parent.getLayoutManager() instanceof GridLayoutManager)) { 85 | return; 86 | } 87 | 88 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 89 | int position = parent.getChildAdapterPosition(view); 90 | int spanCount = layoutManager.getSpanCount(); 91 | int column = position % spanCount + 1;//第几列 92 | int totalCount = parent.getAdapter().getItemCount(); 93 | 94 | if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) { 95 | outRect.top = 0; 96 | outRect.bottom = dividerHelper.getDividerHeight(); 97 | outRect.left = (column - 1) * dividerHelper.getDividerHeight() / spanCount; //左侧为(当前条目数-1)/总条目数*divider宽度 98 | outRect.right = (spanCount - column) * dividerHelper.getDividerHeight() / spanCount;//右侧为(总条目数-当前条目数)/总条目数*divider宽度 99 | } else { 100 | column = position / spanCount + 1;//第几列 101 | int totalColumn = totalCount / spanCount + (totalCount % spanCount == 0 ? 0 : 1);//总列数 102 | outRect.top = 0; 103 | outRect.bottom = dividerHelper.getDividerHeight(); 104 | outRect.left = (column - 1) * dividerHelper.getDividerHeight() / totalColumn; //左侧为(当前条目数-1)/总条目数*divider宽度 105 | outRect.right = (totalColumn - column) * dividerHelper.getDividerHeight() / totalColumn;//右侧为(总条目数-当前条目数)/总条目数*divider宽度 106 | } 107 | } 108 | 109 | public static void onGridDraw(Canvas c, RecyclerView parent, IDivider dividerHelper) { 110 | if (!(parent.getLayoutManager() instanceof GridLayoutManager)) { 111 | return; 112 | } 113 | 114 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 115 | if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) { 116 | drawGridVertical(c, parent, dividerHelper); 117 | } else { 118 | drawGridHorizontal(c, parent, dividerHelper); 119 | } 120 | } 121 | 122 | private static void drawGridVertical(Canvas c, RecyclerView parent, IDivider dividerHelper) { 123 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 124 | float left, top, right, bottom; 125 | int childCount = parent.getChildCount(); 126 | for (int i = 0; i < childCount; i++) { 127 | View child = parent.getChildAt(i); 128 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 129 | int spanCount = layoutManager.getSpanCount(); 130 | int position = parent.getChildAdapterPosition(child); 131 | int column = position % spanCount + 1;//第几列 132 | 133 | //绘制下边 134 | left = child.getLeft() - layoutParams.leftMargin; 135 | right = child.getRight() + layoutParams.rightMargin + dividerHelper.getDividerHeight(); 136 | top = child.getBottom() + layoutParams.bottomMargin; 137 | bottom = top + dividerHelper.getDividerHeight(); 138 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 139 | 140 | //绘制左边,第一列的都不绘制左边 141 | int dividerLeft = (column - 1) * dividerHelper.getDividerHeight() / spanCount; 142 | right = child.getLeft() - layoutParams.leftMargin; 143 | left = right - dividerLeft; 144 | top = child.getTop() - layoutParams.topMargin; 145 | bottom = child.getBottom() + layoutParams.bottomMargin; 146 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 147 | 148 | //绘制右边,最后一列都不绘制右边 149 | left = child.getRight() + layoutParams.rightMargin; 150 | right = left + (spanCount - column) * dividerHelper.getDividerHeight() / spanCount; 151 | top = child.getTop() - layoutParams.topMargin; 152 | bottom = child.getBottom() + layoutParams.bottomMargin; 153 | if (position == parent.getAdapter().getItemCount() - 1 && spanCount != column) { 154 | right = left + dividerHelper.getDividerHeight(); 155 | } 156 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 157 | } 158 | } 159 | 160 | private static void drawGridHorizontal(Canvas c, RecyclerView parent, IDivider dividerHelper) { 161 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 162 | float left, top, right, bottom; 163 | int childCount = parent.getChildCount(); 164 | for (int i = 0; i < childCount; i++) { 165 | View child = parent.getChildAt(i); 166 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 167 | int spanCount = layoutManager.getSpanCount(); 168 | int position = parent.getChildAdapterPosition(child); 169 | int column = position / spanCount + 1;//第几列 170 | int totalCount = parent.getAdapter().getItemCount(); 171 | int totalColumn = totalCount / spanCount + (totalCount % spanCount == 0 ? 0 : 1);//总列数 172 | 173 | //绘制下边 174 | left = child.getLeft() - layoutParams.leftMargin; 175 | right = child.getRight() + layoutParams.rightMargin + dividerHelper.getDividerHeight(); 176 | top = child.getBottom() + layoutParams.bottomMargin; 177 | bottom = top + dividerHelper.getDividerHeight(); 178 | if (column != 1) { 179 | left = left - (column - 1) * dividerHelper.getDividerHeight() / totalColumn;//避免view被重用是,左边被回收 180 | } 181 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 182 | 183 | //绘制左边,第一列不画 184 | right = child.getLeft() - layoutParams.leftMargin; 185 | left = right - (column - 1) * dividerHelper.getDividerHeight() / totalColumn; 186 | top = child.getTop() - layoutParams.topMargin; 187 | bottom = child.getBottom() + layoutParams.bottomMargin; 188 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 189 | 190 | //绘制右边,最后一列不画 191 | left = child.getRight() + layoutParams.rightMargin; 192 | right = left + (totalColumn - column) * dividerHelper.getDividerHeight() / totalColumn; 193 | top = child.getTop() - layoutParams.topMargin; 194 | bottom = child.getBottom() + layoutParams.bottomMargin; 195 | if (column == totalColumn - 1 && position + spanCount > totalCount - 1) { 196 | right = left + dividerHelper.getDividerHeight(); 197 | } 198 | c.drawRect(left, top, right, bottom, dividerHelper.getDividerPaint()); 199 | } 200 | } 201 | //--GridDividerItemDecoration -- end 202 | 203 | //--StaggeredDividerItemDecoration -- start 204 | public static void getStaggeredItemOffset(Rect outRect, RecyclerView parent, View view, IDivider dividerHelper) { 205 | if (!(parent.getLayoutManager() instanceof StaggeredGridLayoutManager)) { 206 | return; 207 | } 208 | 209 | StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) parent.getLayoutManager(); 210 | int spanCount = layoutManager.getSpanCount(); 211 | 212 | if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) { 213 | StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); 214 | outRect.top = 0; 215 | outRect.bottom = dividerHelper.getDividerHeight(); 216 | outRect.left = 0; 217 | outRect.right = (layoutParams.getSpanIndex() + 1) % spanCount == 0 ? 0 : dividerHelper.getDividerHeight(); 218 | } else { 219 | int position = parent.getChildAdapterPosition(view); 220 | int totalCount = parent.getAdapter().getItemCount(); 221 | int column = position / spanCount + 1; 222 | int totalColumn = totalCount / spanCount + (totalCount % spanCount == 0 ? 0 : 1); 223 | outRect.top = 0; 224 | outRect.bottom = dividerHelper.getDividerHeight(); 225 | outRect.left = 0; 226 | outRect.right = column == totalColumn ? 0 : dividerHelper.getDividerHeight(); 227 | } 228 | } 229 | 230 | public static void onStaggeredDraw(RecyclerView parent, IDivider dividerHelper) { 231 | if (!(parent.getLayoutManager() instanceof StaggeredGridLayoutManager)) { 232 | return; 233 | } 234 | parent.setBackgroundColor(dividerHelper.getDividerColor()); 235 | } 236 | 237 | //--StaggeredDividerItemDecoration -- end 238 | 239 | //--StickyLinearDividerItemDecoration -- start 240 | @SuppressWarnings("SuspiciousNameCombination") 241 | public static void getStickyLinearItemOffset(Rect outRect, RecyclerView parent, View view, IStickyHeader stickyDividerHelper) { 242 | if (!(parent.getLayoutManager() instanceof LinearLayoutManager)) { 243 | return; 244 | } 245 | 246 | getLinearItemOffset(outRect, view, parent, stickyDividerHelper); 247 | 248 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 249 | if (callback == null) { 250 | return; 251 | } 252 | int position = parent.getChildAdapterPosition(view); 253 | GroupData groupInfo = callback.getGroupData(position); 254 | if (groupInfo == null) { 255 | return; 256 | } 257 | 258 | LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); 259 | boolean isVertical = layoutManager.getOrientation() == LinearLayoutManager.VERTICAL; 260 | 261 | if (groupInfo.isFirstViewInGroup()) {//如果是分组的第一个,那么外边框的上边界要往上移 262 | View sectionView = callback.getStickyHeaderView(position); 263 | sectionView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 264 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 265 | 266 | if (isVertical) { 267 | stickyDividerHelper.setHeaderHeight(sectionView.getMeasuredHeight()); 268 | outRect.top = stickyDividerHelper.getHeaderHeight(); 269 | } else { 270 | stickyDividerHelper.setHeaderHeight(sectionView.getMeasuredWidth()); 271 | outRect.left = stickyDividerHelper.getHeaderHeight(); 272 | } 273 | 274 | if (groupInfo.isLastViewInGroup()) {//如果同时还是最后一个,把外边框的下边界设为0,免得有divider 275 | if (isVertical) { 276 | outRect.bottom = 0; 277 | } else { 278 | outRect.right = 0; 279 | } 280 | } 281 | } else if (groupInfo.isLastViewInGroup()) {//如果只是分组最后一个,那么上下边界都设为0,即无header也无divider 282 | if (isVertical) { 283 | outRect.top = 0; 284 | outRect.bottom = 0; 285 | } else { 286 | outRect.left = 0; 287 | outRect.right = 0; 288 | } 289 | } else {//既不是分组的第一个也不是最后一个,那么就只有divider 290 | if (isVertical) { 291 | outRect.top = 0; 292 | } else { 293 | outRect.left = 0; 294 | } 295 | } 296 | } 297 | 298 | public static void onStickyLinearDrawOver(Canvas c, RecyclerView parent, IStickyHeader stickyDividerHelper, SparseIntArray headerTop) { 299 | if (!(parent.getLayoutManager() instanceof LinearLayoutManager)) { 300 | return; 301 | } 302 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 303 | if (callback == null) { 304 | return; 305 | } 306 | //因为RecyclerView是复用item的,所以这个数量就是屏幕内能显示出来的item的数量 307 | int childCount = parent.getChildCount(); 308 | LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); 309 | boolean isVertical = layoutManager.getOrientation() == LinearLayoutManager.VERTICAL; 310 | for (int i = 0; i < childCount; i++) { 311 | if (isVertical) { 312 | drawStickyLinearHeaderVertical(c, parent, i, stickyDividerHelper, headerTop); 313 | } else { 314 | drawStickyLinearHeaderHorizontal(c, parent, i, stickyDividerHelper, headerTop); 315 | } 316 | } 317 | } 318 | 319 | /** 320 | * @param index 在屏幕中的位置 321 | */ 322 | private static void drawStickyLinearHeaderVertical(Canvas c, RecyclerView parent, int index, IStickyHeader stickyDividerHelper, SparseIntArray headerTop) { 323 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 324 | if (callback == null) { 325 | return; 326 | } 327 | View view = parent.getChildAt(index); 328 | int position = parent.getChildAdapterPosition(view); 329 | GroupData groupData = callback.getGroupData(position); 330 | if (groupData == null) { 331 | return; 332 | } 333 | 334 | int left = parent.getPaddingLeft(); 335 | int right = parent.getWidth() - parent.getPaddingRight(); 336 | int top; 337 | int bottom; 338 | 339 | if (index != 0) { 340 | if (groupData.isFirstViewInGroup()) {//如果是分组的第一个 341 | top = view.getTop() - stickyDividerHelper.getHeaderHeight(); 342 | bottom = view.getTop(); 343 | } else { 344 | //不是屏幕的第一个并且不是分组的第一个就不需要绘制header 345 | return; 346 | } 347 | } else { 348 | top = parent.getPaddingTop();//如果是屏幕中的第一个,就应该在父容器的顶部 349 | if (groupData.isLastViewInGroup()) {//如果这时候它又是这个分组的最后一个,他就会被下一个分组顶上去 350 | int realTop = view.getBottom() - stickyDividerHelper.getHeaderHeight(); 351 | if (realTop <= top) { 352 | top = realTop; 353 | } 354 | } 355 | bottom = top + stickyDividerHelper.getHeaderHeight(); 356 | } 357 | 358 | View headerView = callback.getStickyHeaderView(position); 359 | headerView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 360 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 361 | headerView.setDrawingCacheEnabled(true); 362 | headerView.layout(left, top, right, bottom); 363 | c.drawBitmap(headerView.getDrawingCache(), left, top, null); 364 | headerTop.put(position, top); 365 | } 366 | 367 | /** 368 | * @param index 在屏幕中的位置 369 | */ 370 | private static void drawStickyLinearHeaderHorizontal(Canvas c, RecyclerView parent, int index, IStickyHeader stickyDividerHelper, SparseIntArray headerTop) { 371 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 372 | if (callback == null) { 373 | return; 374 | } 375 | View view = parent.getChildAt(index); 376 | int position = parent.getChildAdapterPosition(view); 377 | GroupData groupData = callback.getGroupData(position); 378 | if (groupData == null) { 379 | return; 380 | } 381 | 382 | int top = parent.getPaddingTop(); 383 | int bottom = parent.getHeight() - parent.getPaddingBottom(); 384 | int left; 385 | int right; 386 | 387 | if (index != 0) { 388 | if (groupData.isFirstViewInGroup()) {//如果是分组的第一个 389 | left = view.getLeft() - stickyDividerHelper.getHeaderHeight(); 390 | right = view.getLeft(); 391 | } else { 392 | //不是屏幕的第一个并且不是分组的第一个就不需要绘制header 393 | return; 394 | } 395 | } else { 396 | left = parent.getPaddingLeft();//如果是屏幕中的第一个,就应该在父容器的顶部 397 | if (groupData.isLastViewInGroup()) {//如果这时候它又是这个分组的最后一个,他就会被下一个分组顶上去 398 | int realLeft = view.getRight() - stickyDividerHelper.getHeaderHeight(); 399 | if (realLeft <= left) { 400 | left = realLeft; 401 | } 402 | } 403 | right = left + stickyDividerHelper.getHeaderHeight(); 404 | } 405 | 406 | View headerView = callback.getStickyHeaderView(position); 407 | headerView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 408 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 409 | headerView.setDrawingCacheEnabled(true); 410 | headerView.layout(left, top, right, bottom); 411 | c.drawBitmap(headerView.getDrawingCache(), left, top, null); 412 | headerTop.put(position, top); 413 | } 414 | //--StickyLinearDividerItemDecoration -- end 415 | 416 | //--StickyGridDividerItemDecoration -- start 417 | @SuppressWarnings("SuspiciousNameCombination") 418 | public static boolean getStickyGridItemOffset(Rect outRect, RecyclerView parent, View view, IStickyHeader stickyDividerHelper) { 419 | if (checkStickyHeader(parent, stickyDividerHelper)) { 420 | return false; 421 | } 422 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 423 | final int spanCount = layoutManager.getSpanCount(); 424 | int position = parent.getChildAdapterPosition(view); 425 | GroupData data = stickyDividerHelper.getCallback().getGroupData(position); 426 | if (data == null) { 427 | return false; 428 | } 429 | //设置分割线 430 | int column = data.getPosition() % spanCount + 1;//第几列 431 | outRect.top = 0; 432 | outRect.bottom = stickyDividerHelper.getDividerHeight(); 433 | outRect.left = (column - 1) * stickyDividerHelper.getDividerHeight() / spanCount; //左侧为(当前条目数-1)/总条目数*divider宽度 434 | outRect.right = (spanCount - column) * stickyDividerHelper.getDividerHeight() / spanCount;//右侧为(总条目数-当前条目数)/总条目数*divider宽度 435 | if (data.getPosition() == data.getGroupLength() - 1) { 436 | outRect.right = 0; 437 | } 438 | 439 | //添加header的偏移,并去掉某些不需要的分割线 440 | if (data.isFirstLineInGroup(spanCount)) {//如果是分组的第一个,那么外边框的上边界要往上移 441 | View sectionView = stickyDividerHelper.getCallback().getStickyHeaderView(position); 442 | sectionView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 443 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 444 | stickyDividerHelper.setHeaderHeight(sectionView.getMeasuredHeight()); 445 | outRect.top = stickyDividerHelper.getHeaderHeight(); 446 | 447 | if (data.isLastLineInGroup(spanCount)) {//如果同时还是最后一个,把外边框的下边界设为0,免得有divider 448 | outRect.bottom = 0; 449 | } 450 | } else if (data.isLastLineInGroup(spanCount)) {//如果只是分组最后一个,那么上下边界都设为0,即无header也无divider 451 | outRect.top = 0; 452 | outRect.bottom = 0; 453 | } else {//既不是分组的第一个也不是最后一个,那么就只有divider 454 | outRect.top = 0; 455 | } 456 | return true; 457 | } 458 | 459 | public static void onStickyGridDraw(Canvas c, RecyclerView parent, IStickyHeader stickyDividerHelper) { 460 | if (checkStickyHeader(parent, stickyDividerHelper)) { 461 | return; 462 | } 463 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 464 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 465 | 466 | float left, top, right, bottom; 467 | int childCount = parent.getChildCount(); 468 | for (int i = 0; i < childCount; i++) { 469 | View child = parent.getChildAt(i); 470 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); 471 | int spanCount = layoutManager.getSpanCount(); 472 | int position = parent.getChildAdapterPosition(child); 473 | GroupData data = callback.getGroupData(position); 474 | if (data == null) { 475 | continue; 476 | } 477 | int column = data.getPosition() % spanCount + 1;//第几列 478 | 479 | //绘制下边 480 | left = child.getLeft() - layoutParams.leftMargin; 481 | right = child.getRight() + layoutParams.rightMargin + stickyDividerHelper.getDividerHeight(); 482 | top = child.getBottom() + layoutParams.bottomMargin; 483 | bottom = top + stickyDividerHelper.getDividerHeight(); 484 | c.drawRect(left, top, right, bottom, stickyDividerHelper.getDividerPaint()); 485 | 486 | //绘制左边,第一列的都不绘制左边 487 | int dividerLeft = (column - 1) * stickyDividerHelper.getDividerHeight() / spanCount; 488 | right = child.getLeft() - layoutParams.leftMargin; 489 | left = right - dividerLeft; 490 | top = child.getTop() - layoutParams.topMargin; 491 | bottom = child.getBottom() + layoutParams.bottomMargin; 492 | c.drawRect(left, top, right, bottom, stickyDividerHelper.getDividerPaint()); 493 | 494 | //绘制右边,最后一列都不绘制右边 495 | left = child.getRight() + layoutParams.rightMargin; 496 | right = left + (spanCount - column) * stickyDividerHelper.getDividerHeight() / spanCount; 497 | top = child.getTop() - layoutParams.topMargin; 498 | bottom = child.getBottom() + layoutParams.bottomMargin; 499 | if (position == parent.getAdapter().getItemCount() - 1 && spanCount != column) { 500 | right = left + stickyDividerHelper.getDividerHeight(); 501 | } 502 | c.drawRect(left, top, right, bottom, stickyDividerHelper.getDividerPaint()); 503 | } 504 | } 505 | 506 | public static void onStickyGridDrawOver(Canvas c, RecyclerView parent, IStickyHeader stickyDividerHelper, SparseIntArray headersTop) { 507 | if (checkStickyHeader(parent, stickyDividerHelper)) { 508 | return; 509 | } 510 | 511 | int childCount = parent.getChildCount(); 512 | for (int index = 0; index < childCount; index++) { 513 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 514 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 515 | View view = parent.getChildAt(index); 516 | int realPos = parent.getChildAdapterPosition(view); 517 | GroupData data = callback.getGroupData(realPos); 518 | if (data == null) { 519 | return; 520 | } 521 | 522 | final int spanCount = layoutManager.getSpanCount(); 523 | 524 | int left = parent.getPaddingLeft(); 525 | int right = parent.getWidth() - parent.getPaddingRight(); 526 | int top; 527 | int bottom; 528 | 529 | if (index != 0) { 530 | if (data.isFirstLineInGroup(spanCount) && index >= spanCount) {//如果是分组的第一个 531 | top = view.getTop() - stickyDividerHelper.getHeaderHeight(); 532 | bottom = view.getTop(); 533 | } else { 534 | //不是屏幕的第一个并且不是分组的第一个就不需要绘制header 535 | continue; 536 | } 537 | } else { 538 | top = parent.getPaddingTop();//如果是屏幕中的第一个,就应该在父容器的顶部 539 | if (data.isLastLineInGroup(spanCount)) {//如果这时候它又是这个分组的最后一个,他就会被下一个分组顶上去 540 | int realTop = view.getBottom() - stickyDividerHelper.getHeaderHeight(); 541 | if (realTop <= top) { 542 | top = realTop; 543 | } 544 | } 545 | bottom = top + stickyDividerHelper.getHeaderHeight(); 546 | } 547 | 548 | View sectionView = callback.getStickyHeaderView(realPos); 549 | sectionView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 550 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 551 | 552 | sectionView.setDrawingCacheEnabled(true); 553 | sectionView.layout(left, top, right, bottom); 554 | c.drawBitmap(sectionView.getDrawingCache(), left, top, null); 555 | headersTop.put(realPos, top); 556 | } 557 | } 558 | 559 | /** 560 | * 不符合数据要求则返回true,就不处理,反之亦然 561 | */ 562 | private static boolean checkStickyHeader(RecyclerView parent, IStickyHeader stickyDividerHelper) { 563 | if (!(parent.getLayoutManager() instanceof GridLayoutManager)) { 564 | return true; 565 | } 566 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 567 | if (layoutManager.getOrientation() == GridLayoutManager.HORIZONTAL) { 568 | return true; 569 | } 570 | StickyDividerCallback callback = stickyDividerHelper.getCallback(); 571 | return callback == null; 572 | } 573 | //--StickyGridDividerItemDecoration -- end 574 | 575 | private static boolean dontShowLastDivider(View view, RecyclerView parent, IDivider dividerHelper) { 576 | return parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1 && !dividerHelper.isShowLastDivider(); 577 | } 578 | 579 | public static class DividerHelper implements IDivider { 580 | private Paint dividerPaint; 581 | private int dividerHeight; 582 | private int dividerColor; 583 | private boolean showLastDivider; 584 | 585 | DividerHelper(ItemDecorationFactory.DividerBuilder builder) { 586 | this.dividerHeight = builder.getDividerHeight(); 587 | this.dividerColor = builder.getDividerColor(); 588 | this.showLastDivider = builder.isShowLastDivider(); 589 | 590 | initPaint(); 591 | } 592 | 593 | private void initPaint() { 594 | dividerPaint = new Paint(); 595 | dividerPaint.setAntiAlias(true); 596 | dividerPaint.setColor(dividerColor); 597 | } 598 | 599 | @Override 600 | public Paint getDividerPaint() { 601 | return dividerPaint; 602 | } 603 | 604 | @Override 605 | public int getDividerHeight() { 606 | return dividerHeight; 607 | } 608 | 609 | @Override 610 | public int getDividerColor() { 611 | return dividerColor; 612 | } 613 | 614 | @Override 615 | public boolean isShowLastDivider() { 616 | return showLastDivider; 617 | } 618 | } 619 | 620 | public static class StickyDividerHelper implements IStickyHeader { 621 | private Paint dividerPaint; 622 | private int dividerHeight; 623 | private int dividerColor; 624 | private boolean showLastDivider; 625 | 626 | private StickyDividerCallback callback; 627 | private int headerHeight; 628 | 629 | StickyDividerHelper(ItemDecorationFactory.StickyDividerBuilder builder) { 630 | this.callback = builder.getCallback(); 631 | this.dividerHeight = builder.getDividerHeight(); 632 | this.dividerColor = builder.getDividerColor(); 633 | this.showLastDivider = builder.isShowLastDivider(); 634 | 635 | initPaint(); 636 | } 637 | 638 | private void initPaint() { 639 | dividerPaint = new Paint(); 640 | dividerPaint.setAntiAlias(true); 641 | dividerPaint.setColor(dividerColor); 642 | } 643 | 644 | @Override 645 | public Paint getDividerPaint() { 646 | return dividerPaint; 647 | } 648 | 649 | @Override 650 | public int getDividerHeight() { 651 | return dividerHeight; 652 | } 653 | 654 | @Override 655 | public void setHeaderHeight(int headerHeight) { 656 | this.headerHeight = headerHeight; 657 | } 658 | 659 | @Override 660 | public int getDividerColor() { 661 | return dividerColor; 662 | } 663 | 664 | @Override 665 | public boolean isShowLastDivider() { 666 | return showLastDivider; 667 | } 668 | 669 | public int getHeaderHeight() { 670 | return headerHeight; 671 | } 672 | 673 | @Override 674 | public StickyDividerCallback getCallback() { 675 | return callback; 676 | } 677 | } 678 | 679 | public interface IDivider { 680 | Paint getDividerPaint(); 681 | 682 | int getDividerHeight(); 683 | 684 | int getDividerColor(); 685 | 686 | boolean isShowLastDivider(); 687 | } 688 | 689 | public interface IStickyHeader extends IDivider { 690 | StickyDividerCallback getCallback(); 691 | 692 | int getHeaderHeight(); 693 | 694 | void setHeaderHeight(int headerHeight); 695 | } 696 | } 697 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/LinearDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by arvinljw on 2018/7/23 09:39 10 | * Function: 11 | * Desc:LinearLayoutManager对应的分割线 12 | */ 13 | public class LinearDividerItemDecoration extends BaseDividerItemDecoration { 14 | 15 | LinearDividerItemDecoration(ItemDecorationFactory.DividerBuilder builder) { 16 | super(builder); 17 | } 18 | 19 | @Override 20 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 21 | super.getItemOffsets(outRect, view, parent, state); 22 | ItemDecorationHelper.getLinearItemOffset(outRect, view, parent, dividerHelper); 23 | } 24 | 25 | @Override 26 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 27 | super.onDraw(c, parent, state); 28 | ItemDecorationHelper.onLinearDraw(c, parent, dividerHelper); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/OnHeaderClickListener.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | /** 4 | * Created by arvinljw on 2018/7/27 14:09 5 | * Function: 6 | * Desc: 7 | */ 8 | public interface OnHeaderClickListener { 9 | void onHeaderClicked(int position); 10 | } 11 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/StaggeredGridDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by arvinljw on 2018/7/23 09:39 10 | * Function: 11 | * Desc:StaggeredGridLayoutManager对应的分割线 12 | */ 13 | public class StaggeredGridDividerItemDecoration extends BaseDividerItemDecoration { 14 | StaggeredGridDividerItemDecoration(ItemDecorationFactory.DividerBuilder builder) { 15 | super(builder); 16 | } 17 | 18 | @Override 19 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 20 | super.getItemOffsets(outRect, view, parent, state); 21 | ItemDecorationHelper.getStaggeredItemOffset(outRect, parent, view, dividerHelper); 22 | } 23 | 24 | @Override 25 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 26 | super.onDraw(c, parent, state); 27 | ItemDecorationHelper.onStaggeredDraw(parent, dividerHelper); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/StickyDividerCallback.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by arvinljw on 2018/7/24 15:28 7 | * Function: 8 | * Desc: 9 | */ 10 | public interface StickyDividerCallback { 11 | 12 | GroupData getGroupData(int position); 13 | 14 | View getStickyHeaderView(int position); 15 | } 16 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/StickyGridDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import androidx.recyclerview.widget.GridLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import android.view.View; 8 | 9 | /** 10 | * Created by arvinljw on 2018/7/24 16:35 11 | * Function: 12 | * Desc: 13 | */ 14 | public class StickyGridDividerItemDecoration extends BaseStickyDividerItemDecoration { 15 | private GridLayoutManager.SpanSizeLookup lookup; 16 | 17 | StickyGridDividerItemDecoration(ItemDecorationFactory.StickyDividerBuilder builder) { 18 | super(builder); 19 | } 20 | 21 | @SuppressWarnings("SuspiciousNameCombination") 22 | @Override 23 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 24 | super.getItemOffsets(outRect, view, parent, state); 25 | 26 | if (ItemDecorationHelper.getStickyGridItemOffset(outRect, parent, view, stickyDividerHelper)) { 27 | setSpanSizeLookup(parent); 28 | } 29 | } 30 | 31 | private void setSpanSizeLookup(RecyclerView parent) { 32 | GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 33 | final int spanCount = layoutManager.getSpanCount(); 34 | if (lookup == null) { 35 | lookup = new GridLayoutManager.SpanSizeLookup() {//相当于weight 36 | @Override 37 | public int getSpanSize(int position) { 38 | GroupData groupData = stickyDividerHelper.getCallback().getGroupData(position); 39 | int returnSpan = 1; 40 | if (groupData != null && groupData.isLastViewInGroup()) { 41 | returnSpan = spanCount - groupData.getPosition() % spanCount; 42 | } 43 | return returnSpan; 44 | } 45 | }; 46 | } 47 | layoutManager.setSpanSizeLookup(lookup); 48 | } 49 | 50 | @Override 51 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 52 | super.onDraw(c, parent, state); 53 | ItemDecorationHelper.onStickyGridDraw(c, parent, stickyDividerHelper); 54 | } 55 | 56 | @Override 57 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 58 | super.onDrawOver(c, parent, state); 59 | ItemDecorationHelper.onStickyGridDrawOver(c, parent, stickyDividerHelper, headersTop); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/StickyHeaderClickGestureDetector.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.content.Context; 4 | import android.util.SparseIntArray; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by arvinljw on 2018/7/27 11:14 10 | * Function: 11 | * Desc: 12 | */ 13 | public class StickyHeaderClickGestureDetector extends GestureDetector.SimpleOnGestureListener { 14 | private GestureDetector gestureDetector; 15 | 16 | private SparseIntArray headersTop; 17 | private ItemDecorationHelper.StickyDividerHelper stickyDividerHelper; 18 | private OnHeaderClickListener headerClickListener; 19 | 20 | StickyHeaderClickGestureDetector(Context context, SparseIntArray headersTop, ItemDecorationHelper.StickyDividerHelper stickyDividerHelper) { 21 | gestureDetector = new GestureDetector(context, this); 22 | this.headersTop = headersTop; 23 | this.stickyDividerHelper = stickyDividerHelper; 24 | } 25 | 26 | public void setOnHeaderClickListener(OnHeaderClickListener headerClickListener) { 27 | this.headerClickListener = headerClickListener; 28 | } 29 | 30 | public boolean onTouchEvent(MotionEvent event) { 31 | return gestureDetector.onTouchEvent(event); 32 | } 33 | 34 | @Override 35 | public boolean onSingleTapUp(MotionEvent e) { 36 | for (int i = 0; i < headersTop.size(); i++) { 37 | int position = headersTop.keyAt(i); 38 | int top = headersTop.get(position); 39 | float y = e.getY(); 40 | if (y >= top && y <= top + stickyDividerHelper.getHeaderHeight()) { 41 | if (headerClickListener != null) { 42 | headerClickListener.onHeaderClicked(position); 43 | } 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/java/net/arvin/itemdecorationhelper/StickyLinearDividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package net.arvin.itemdecorationhelper; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | 7 | import android.view.View; 8 | 9 | /** 10 | * Created by arvinljw on 2018/7/24 14:45 11 | * Function: 12 | * Desc:线性粘性头部,水平垂直都可以 13 | */ 14 | public class StickyLinearDividerItemDecoration extends BaseStickyDividerItemDecoration { 15 | 16 | StickyLinearDividerItemDecoration(ItemDecorationFactory.StickyDividerBuilder builder) { 17 | super(builder); 18 | } 19 | 20 | @Override 21 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 22 | super.getItemOffsets(outRect, view, parent, state); 23 | ItemDecorationHelper.getStickyLinearItemOffset(outRect, parent, view, stickyDividerHelper); 24 | } 25 | 26 | @Override 27 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 28 | super.onDraw(c, parent, state); 29 | ItemDecorationHelper.onLinearDraw(c, parent, stickyDividerHelper); 30 | } 31 | 32 | @Override 33 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 34 | super.onDrawOver(c, parent, state); 35 | ItemDecorationHelper.onStickyLinearDrawOver(c, parent, stickyDividerHelper, headersTop); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/res/layout/item_decoration_default_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /itemdecorationhelper/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | itemdecorationhelper 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':itemdecorationhelper' 2 | --------------------------------------------------------------------------------