├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gersion │ │ └── multiselecter │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── gersion │ │ │ └── multiselecter │ │ │ ├── GlideImageLoader.java │ │ │ ├── ImageUtil.java │ │ │ ├── MainActivity.java │ │ │ ├── MultiSelectActivity.java │ │ │ ├── SingleSelectActivity.java │ │ │ ├── TitleBean.java │ │ │ ├── UserAdapter.java │ │ │ └── UserBean.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_multi_select.xml │ │ ├── activity_single_select.xml │ │ ├── item_title.xml │ │ └── item_user.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_access_time_purple_200_24dp.png │ │ ├── ic_archive_purple_200_24dp.png │ │ ├── ic_assignment_turned_in_purple_200_24dp.png │ │ ├── ic_backup_purple_200_24dp.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_lock_purple_200_24dp.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── gersion │ └── multiselecter │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gersion │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── gersion │ │ │ └── library │ │ │ ├── MultiSelecter.java │ │ │ ├── adapter │ │ │ ├── BaseMultiAdapter.java │ │ │ └── SelectIconRvAdapter.java │ │ │ ├── bean │ │ │ └── FloatImgBean.java │ │ │ ├── fragment │ │ │ └── SelectionFragment.java │ │ │ ├── inter │ │ │ ├── Filter.java │ │ │ ├── IImageLoader.java │ │ │ ├── IMatch.java │ │ │ ├── ItemFilter.java │ │ │ ├── OnItemClickListener.java │ │ │ ├── OnLastItemLoadListener.java │ │ │ └── OnSelectItemChangeListener.java │ │ │ ├── multitype │ │ │ ├── adapter │ │ │ │ └── MultiTypeAdapter.java │ │ │ ├── inter │ │ │ │ └── IMultiLayout.java │ │ │ ├── typepool │ │ │ │ ├── MultiBeanListPool.java │ │ │ │ ├── MultiLayoutListPool.java │ │ │ │ ├── MultiTypePoolList.java │ │ │ │ ├── MultiTypePoolMap.java │ │ │ │ └── TypePool.java │ │ │ └── viewholder │ │ │ │ └── BaseViewHolder.java │ │ │ ├── utils │ │ │ ├── MatchUtils.java │ │ │ ├── ScreenUtils.java │ │ │ └── SizeUtils.java │ │ │ └── view │ │ │ ├── MultiSelectView.java │ │ │ └── smartrecycleview │ │ │ ├── IRVAdapter.java │ │ │ ├── SmartRecycleView.java │ │ │ ├── SmartRecycler.java │ │ │ └── ptr2 │ │ │ ├── CustomLoadLayout.java │ │ │ ├── IRVAdapter.java │ │ │ ├── OnRetryListener.java │ │ │ ├── PullToRefreshLayout.java │ │ │ └── SuperSwipeRefreshLayout.java │ └── res │ │ ├── drawable-xhdpi │ │ ├── check_false.png │ │ ├── check_true.png │ │ ├── down_arrow.png │ │ └── geycheck.png │ │ ├── drawable │ │ ├── add_friend_circle_selector.xml │ │ ├── bg_friend_circle_add.xml │ │ ├── btn_check.xml │ │ ├── line_edit_text.xml │ │ └── public_btn_bg_queding.xml │ │ ├── layout │ │ ├── fragment_selection.xml │ │ ├── item_icon.xml │ │ ├── item_user.xml │ │ ├── view_falied.xml │ │ ├── view_load_more.xml │ │ ├── view_loading.xml │ │ ├── view_no_data.xml │ │ └── view_refresh.xml │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── gersion │ └── library │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | *.idea 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild 49 | 50 | # Google Services (e.g. APIs or Firebase) 51 | google-services.json 52 | 53 | # Freeline 54 | freeline.py 55 | freeline/ 56 | freeline_project_description.json 57 | -------------------------------------------------------------------------------- /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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###### 先上图,看看今天开什么车,坐稳咯。拖拉机即将超速行使,请系好安全带! 2 | ![MultiSelector.gif](http://upload-images.jianshu.io/upload_images/2673171-568b55a3bb4190c9.gif?imageMogr2/auto-orient/strip) 3 | 4 | ##### 如何使用? 5 | 在项目级build.gradle加入 6 | ``` 7 | allprojects { 8 | repositories { 9 | ... 10 | maven { url 'https://jitpack.io' } 11 | } 12 | } 13 | ``` 14 | 在module级build.gradle加入 15 | ``` 16 | compile 'com.github.GaoGersy:MultiSelecter:1.0.1' 17 | ``` 18 | >先来分析一下android中会遇到哪些选择方面的需求: 19 | 1. 单选--这个就不啰嗦了 20 | 2. 多选: 21 | 1. 全部数据都可以被选择,这个比较简单 22 | 2. 过滤掉一部分数据,不让显示出来 23 | 3. 过滤掉一部分数据,能显示,但用户不能选择 24 | 4. 默认选择一部分数据,用户可以取消选择 25 | 5. 默认选择一部分数据,用户不可以取消选择 26 | >原谅我还只遇到过这些,那这里都有哪些功能呢? 27 | 28 | 1. 多选时会有选择过渡动画(如GIF里所见,但肯定不能肯的这样掉渣的) 29 | 2. 点击上面的头像可以取消选择 30 | 3. 选中的数据条数提示 31 | 4. 根据选择的条目会将搜索栏向右移动,搜索样最少占屏幕宽度的1/3 32 | 5. 当然还有模糊搜索功能啦 33 | 6. 全选 34 | >啰啰嗦嗦讲了一堆,看一下Gif图就什么都知道了好么? 35 | 好吧,遇到像你们这些高智商的,还是直接开车好了 36 | 37 | ###### 一、缘起: 38 | 早上9点,程序猿小猿同学一如往常准时的坐在自己的位置上,又是燥热的一天。挤了一个小时的地铁,原本瘦消的身体越发觉得扁平了。小猿心想:我要再挤个一年,也会成A4腰哦,不是像A4纸那么宽,而是像它那么扁。开机-洗水杯-吃早餐,小猿开启了程式化的一天。 39 | 40 | 小猿刚叼起一个小笼包,还没来得及体会里面的美味,产品经理轻轻拍了下他的肩膀。直勾勾的看着小猿眼睛说:“昨天那个选择页面要做一个已经选择条目头像展示的功能。”小猿刚想说我cao,产品经理连忙说,中午下班前看下效果。小猿差点没把小笼包整个咽下去,早TM干吗去了,昨天晚上加班赶出来的,又得改,操蛋玩意儿,小猿心理骂道。 41 | 42 | 快速解决了小笼包,迅速进入到战斗状态。好在这个不是太难实现,只是在数据传递方面要下点功夫。在饭点前总算一切搞掂,翘着二郎腿对产品说,你要的头像展示功能OK了。产品听完悠悠的转过身,肯定道:嗯嗯,不错,是我想要的。不过感觉好像还少了点什么,哦,对了,这个页面条目太多,用户可能看不过来,需要一个搜索功能。放在这些头像同一排就好了,就微信那样。说完拍拍小猿的肩膀,跟其它同事出去吃饭去了,留下小猿在风中凌乱。 43 | 44 | 小猿点了个外卖,然后想着刚刚这个需求要怎么实现。突然小猿用力的拍了一下桌子,这样下去不是办法,以后还哪里敢吃小笼包,只能喝粥度日了。我得赶在那货之前弄个比较全面的解决方案,小猿对自己斩钉截铁的说到。 45 | 46 | 小猿在想着他的对策,首先不能跟某一块耦合了,万一其它地方也要用到呢,那不是尴尬了?功能得尽可能的全面些,多选,数据过滤,单选....甚至还可以来得动画。小猿仿佛看到了自己对于需求应对自如的样子....一丝冷风吹过,吹醒了正在做白日梦的小猿。 47 | ###### 二、 抽象: 48 | 说干就干,首先得解决的是如何解耦,得用接口,接口是对现实的抽象。有了思路剩下的就是手速了,像小猿这种单身20多年的,手速当然快如闪电,要不能上砖石? 49 | 1. 先造一个能判断数据是何种选择类型的接口,返回不同条目类型(可选、不可选 ....),像这样: 50 | ``` 51 | public interface Filter { 52 | 53 | //专为标题而生,因为系统默认给的是0,这样title Bean类里面都不用做过多的修改了 54 | int TITLE_NO_CHOICE = 0; 55 | 56 | //默认不选中,可以选中 57 | int NORMAL = 1; 58 | 59 | //默认选中,不可以取消选中 60 | int SELECTED_NOCANCEL = 2; 61 | 62 | //默认不选中,不能被选中 63 | int NO_CHOICE = 3; 64 | 65 | //不显示在列表 66 | int NOT_SHOW = 4; 67 | 68 | //本地图片地址 69 | int getImageResource(); 70 | 71 | //网络图片url 72 | String getImageUrl(); 73 | 74 | //返回当前条目的状态,就是上面定义的那些个常量,返回值会在BaseViewHolder里面用到 75 | int filter(); 76 | 77 | //是否是选中状态 78 | boolean isSelected(); 79 | 80 | void setSelected(boolean isSelected); 81 | 82 | //是否匹配搜索关键字,用来处理搜索的,如果不要搜索功能,可以不用处理 83 | boolean isMatch(String condition); 84 | } 85 | ``` 86 | >这样只要每个具体bean 类去实现 87 | 在filter方法里去根据不同的条件返回上面定义的常量 88 | isMatch方法是针对模糊搜索设计的 89 | 机智如我,看你还怎么改需求,小猿暗自窃喜... 90 | 91 | 2. 图片加载框架现在有好几个,搞不好哪个以后就不维护了,我可不能在一棵树上吊死,小猿警惕起来,顺手撸了个图片加载框架的接口: 92 | ``` 93 | public interface IImageLoader { 94 | void showImage(Context context, String url, ImageView imageView); 95 | } 96 | ``` 97 | >以后项目想用哪个图片框架只需要根据当前项目使用的图片加载框架实现对应的ImageLoader就可以了,就是这么任性 98 | 99 | ###### 三、实施: 100 | 大致思路是有了,具体要怎么实现这些功能呢?小猿摸着日益上扬的发迹线,沉思良久.... 101 | 1. 不需要展示给用户的数据过滤倒是好做,可以像下面这样的呀: 102 | ``` 103 | protected List getFilterItems(List items) { 104 | mSelectionList.clear(); 105 | if (items != null) { 106 | List data = new ArrayList<>(); 107 | for (T item : items) { 108 | int type = item.filter(); 109 | if (type != Filter.NOT_SHOW) { 110 | data.add(item); 111 | if (type != Filter.NO_CHOICE&&type!=Filter.TITLE_NO_CHOICE) { 112 | mSelectionList.add(item); 113 | } 114 | } 115 | } 116 | return data; 117 | } else { 118 | return null; 119 | } 120 | } 121 | ``` 122 | 2. 如果是正常的数据,既要支持多选又能支持单选,这个要怎么处理比较好呢?毫无头绪,一不小心干掉几根本来就屈指可数的头发,抱着试试的心态写了下面的代码: 123 | ``` 124 | public void setData(T data) { 125 | normalBackgroundResource = getNormalBackgroundResource(); 126 | noChoiceBackgroundResource = getNoChoiceBackgroundResource(); 127 | int type = data.filter(); 128 | if (mCheckBox != null) { 129 | mCheckBox.setChecked(data.isSelected()); 130 | } 131 | if (type == Filter.NORMAL) { 132 | onNormal(data); 133 | } else if (type == Filter.NO_CHOICE) { 134 | onNoChoice(); 135 | } 136 | } 137 | 138 | private void setClickListener(final Filter data) { 139 | itemView.setOnClickListener(new View.OnClickListener() { 140 | @Override 141 | public void onClick(View view) { 142 | if (selectType == SelectionFragment.MULTI_SELECT) { 143 | multiSelect(data); 144 | } else { 145 | singleSelect(data); 146 | } 147 | } 148 | }); 149 | } 150 | 151 | private void singleSelect(Filter data) { 152 | if (mListener == null) { 153 | throw new IllegalStateException("没有设置点击事件监听"); 154 | } 155 | if (mCurrentCheckBox != null) { 156 | mCurrentCheckBox.setChecked(false); 157 | mCurrentItem.setSelected(false); 158 | mListener.onItemClick(itemView, mCurrentItem, false); 159 | } 160 | data.setSelected(true); 161 | mCheckBox.setChecked(true); 162 | mListener.onItemClick(itemView, data, true); 163 | mCurrentCheckBox = mCheckBox; 164 | mCurrentItem = data; 165 | } 166 | ``` 167 | >写完心理在打鼓,不知道好不好使,反正试下又不要钱。经过漫长的等待,程序总算部署完毕,这么个小项目编译居然还两分钟,还天天嫌我效率低,小猿抱怨道。试了下单选功能,Prefect!简直不要太如我意,哈哈。尝到甜头的小猿准备再试试多选功能,我草,还是那么完美。机智如我,啦啦啦....差点唱起了歌 168 | 169 | 3 . 那如果是需要展示,但是不能操作的呢?这个就简单了,随手就撸了一个出来: 170 | ``` 171 | public void onNoChoice() { 172 | if (mCheckBox != null) { 173 | mCheckBox.setBackgroundResource(noChoiceBackgroundResource); 174 | itemView.setOnClickListener(null); 175 | } 176 | } 177 | ``` 178 | >机智如我,啦啦啦....对于小猿大神我来说,都是小菜一碟。小猿得意的哼起了歌,得意得有点欠揍 179 | 180 | ###### 是时候解决产品提的那两个需求了: 181 | 展示头像倒是没难度的,关键是如何让搜索根据选择条目的数量动态改变宽度?能不能计算出当前RecyclerView占用的宽度呢?如果能知道的话事情不就解决了吗?嗯嗯,撸串代码测试一下: 182 | ``` 183 | private void refreshLayout(boolean isSelected) { 184 | mIvSelectAll.setSelected(isSelected); 185 | 186 | if (mAdapter.getSelectionList().size() == mIconListRvAdapter.getItemCount()) { 187 | mIvSelectAll.setSelected(true); 188 | } 189 | 190 | int size = mSelectList.size(); 191 | mTvConfirm.setText(size == 0 ? "确定" : "确定(" + size + ")"); 192 | if (size == 0) { 193 | mTvConfirm.setEnabled(false); 194 | } else { 195 | mTvConfirm.setEnabled(true); 196 | } 197 | 198 | int width = mItemWidth * size; 199 | if (width > mMaxWidth) { 200 | width = mMaxWidth; 201 | } 202 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.WRAP_CONTENT); 203 | mIconRecyclerView.setLayoutParams(params); 204 | mContainer.requestLayout(); 205 | } 206 | ``` 207 | >完美、完美、完美、简直不要太完美。这里主要做三件事: 208 | 如果手动选择时,所有的条目都被选中,全选按钮也会变成选中状态; 209 | 设置确定按钮的选中条目的数量,并根据数量设置确定按钮是否可点击; 210 | 动态改变搜索栏的宽度 211 | mItemWidth是怎么计算的? 212 | ``` 213 | private void initData() { 214 | int screenWidth = ScreenUtils.getScreenWidth(mContext); 215 | mMaxWidth = screenWidth * 2 / 3; 216 | mItemWidth = SizeUtils.dp2px(mContext, 45); 217 | mWidth5 = SizeUtils.dp2px(mContext, 5); 218 | } 219 | ``` 220 | >你没看错,写死了,可惜不能插入捂脸的表情。RecyclerView一个item占用45dp,这个在写布局的时候就已经知道了。当然这种硬编码可不是个好习惯哦,不要学习。 221 | 222 | ###### 四、进化: 223 | 尝到甜头的小猿现在感觉自己无所不能,哎呀,这个效果我还是很不满意呀,为了展示逼格得加个动画不可。经过千辛万苦,各种尝试,总算有了能用的代码: 224 | ``` 225 | public void translationView(final View itemView, final Filter item) { 226 | getParentPoint(); 227 | itemView.setClickable(false); 228 | final FloatImgBean floatImg = getFloatImg(); 229 | floatImg.mImageView.setVisibility(View.VISIBLE); 230 | mPlaceHolder.setVisibility(View.VISIBLE); 231 | floatImg.mImageView.setImageResource(item.getImageResource()); 232 | MultiSelecter.mImageLoader.showImage(mContext, item.getImageUrl(), floatImg.mImageView); 233 | floatImg.mIsAnimator = true; 234 | 235 | int[] sourceLocation = new int[2]; 236 | mSourceView.getLocationOnScreen(sourceLocation); 237 | int startX = sourceLocation[0]; 238 | int startY = sourceLocation[1]; 239 | 240 | int[] tagetLocation = new int[2]; 241 | mIconRecyclerView.getLocationOnScreen(tagetLocation); 242 | int endX = tagetLocation[0] + mIconRecyclerView.getWidth() + mWidth5; 243 | 244 | int endY = tagetLocation[1] + mWidth5 * 2; 245 | 246 | animator(itemView, item, floatImg, startX, startY, endX, endY); 247 | } 248 | 249 | private void animator(final View itemView, final Filter item, final FloatImgBean floatImg, int startX, int startY, int endX, int endY) { 250 | ObjectAnimator animatorX = ObjectAnimator.ofFloat(floatImg.mImageView, "translationX", startX - mStartX, endX - mStartX); 251 | ObjectAnimator animatorY = ObjectAnimator.ofFloat(floatImg.mImageView, "translationY", startY - mStartY, endY - mStartY); 252 | AnimatorSet animatorSet = new AnimatorSet(); 253 | animatorSet.playTogether(animatorX, animatorY); 254 | animatorSet.setDuration(calcDuration(startX - endX, startY - endY)); 255 | animatorSet.setInterpolator(new OvershootInterpolator(1.1f)); 256 | animatorSet.start(); 257 | animatorSet.addListener(new AnimatorListenerAdapter() { 258 | @Override 259 | public void onAnimationEnd(Animator animation) { 260 | floatImg.mImageView.setVisibility(View.GONE); 261 | floatImg.mIsAnimator = false; 262 | mIconListRvAdapter.add(item); 263 | mIconRecyclerView.smoothScrollToPosition(mIconListRvAdapter.getItemCount()); 264 | mPlaceHolder.setVisibility(View.GONE); 265 | refreshLayout(false); 266 | itemView.setClickable(true); 267 | } 268 | }); 269 | } 270 | ``` 271 | >这里小猿可是踩了无数坑,这里可以详细说下。敲黑板,这里可以高潮部分,就不要打瞌睡啦! 272 | 273 | >首先是getLocationOnScreen 这个操蛋的方法,网上一致说这个获得的是当前view相当于整个屏的坐标位置,根据android坐标系的尿性,那屏幕最左上角就是原点咯,那我通过这个方法就是能获得顶部头像栏内条目的绝对坐标了咯,简直不要太爽。 274 | 然并卵,根本不是那么回事好吗?直到现在为止,小猿仍然没有搞懂这个方法获取的坐标会受到哪些因素的影响。但是可以肯定的是如果获取Item的坐标会出现偏移,貌似跟padding有关,有知道的可以在评论区解答。 275 | 但是获取mIconRecyclerView的坐标是没有问题的,这样我只要计算mIconRecyclerView的宽度就可以知道X坐标了。 276 | 277 | >android的属性动画执行过程中,如果执行了另外的属性动画,会中断之前的执行,导致动画监听发生错乱。 278 | 整个逻辑全都乱套了,为此小猿想出了一个奇淫巧技,java不是有线程池么?为什么我不能弄一个ImageView的Pool,装上几个ImageView,没有在执行动画的取出来用,执行动画的让它自己玩去。于是有了下面这个: 279 | ``` 280 | private void initFloatPool() { 281 | FloatImgBean bean1 = new FloatImgBean(); 282 | bean1.mImageView = mFloatImg_1; 283 | FloatImgBean bean2 = new FloatImgBean(); 284 | bean2.mImageView = mFloatImg_2; 285 | FloatImgBean bean3 = new FloatImgBean(); 286 | bean3.mImageView = mFloatImg_3; 287 | mImagePool.add(bean1); 288 | mImagePool.add(bean2); 289 | mImagePool.add(bean3); 290 | } 291 | 292 | private FloatImgBean getFloatImg() { 293 | for (FloatImgBean bean : mImagePool) { 294 | if (!bean.mIsAnimator) { 295 | return bean; 296 | } 297 | } 298 | return null; 299 | } 300 | ``` 301 | >当然这个用FloatImgBean 包装了一下,记录了当前ImageView是否有在执行动画。 302 | 为了让动画的执行更自然作出了一个牺牲,在mIconRecyclerView和搜索栏中间加了一个和mIconRecyclerView 的Item等宽的一个view,在动画执行的过程中设置成VISIBLE或者GONE来迎合动画的执行。 303 | ###### 五、示例: 304 | 完成了所有的功能,小猿瘫坐在电脑椅上,脸上露出满意的微笑。 305 | 忽然小猿虎躯一震,不行,我还得写个示例,不然怎么在人前装逼呢? 306 | 先实现一个Bean 类: 307 | ``` 308 | public class UserBean implements Filter { 309 | public String userName; 310 | public int icon; 311 | public int age; 312 | public boolean isSelected; 313 | public String iconUrl; 314 | 315 | @Override 316 | public boolean isSelected() { 317 | return isSelected; 318 | } 319 | 320 | @Override 321 | public void setSelected(boolean isSelected) { 322 | this.isSelected = isSelected; 323 | } 324 | 325 | @Override 326 | public int getImageResource() { 327 | return icon; 328 | } 329 | 330 | @Override 331 | public String getImageUrl() { 332 | return iconUrl; 333 | } 334 | 335 | @Override 336 | public int filter() { 337 | if (age<3){ 338 | return Filter.NO_CHOICE; 339 | }else if (age>=3&&age<100){ 340 | return Filter.NORMAL; 341 | } 342 | return 0; 343 | } 344 | 345 | @Override 346 | public boolean isMatch(String condition) { 347 | if (MatchUtils.isMatch(userName,condition)){ 348 | return true; 349 | } 350 | return false; 351 | } 352 | } 353 | ``` 354 | 如何调用那封装好的东西呢? 355 | ``` 356 | MultiSelectView selectView = new MultiSelecter.Builder(this, container) 357 | .setImageLoader(new GlideImageLoader()) 358 | .setMultiAdapter(new UserAdapter()) 359 | .setSelectType(MultiSelecter.MULTI_SELECT) 360 | .register(UserBean.class, R.layout.item_user) 361 | .register(TitleBean.class, R.layout.item_title) 362 | .build(); 363 | ``` 364 | 当然还得来一个ImageLoader的示例: 365 | ``` 366 | public class GlideImageLoader implements IImageLoader { 367 | @Override 368 | public void showImage(Context context, String url, ImageView imageView) { 369 | Glide.with(context) 370 | .load(url) 371 | .into(imageView); 372 | } 373 | } 374 | ``` 375 | 哦了,好累,小猿来了个葛优躺.... 376 | 377 | 用到的开源项目: 378 | 379 | **[AutoRecycleView](https://github.com/GaoGersy/AutoRecycleView)**:自动维护下拉刷新和上拉加载更多 380 | 381 | **[SimpleMutiTypeAdapter](https://github.com/GaoGersy/SimpleMutiTypeAdapter)** :使用起来很简单的RecyclerView多条目 382 | 383 | 没错,都是小猿之前为了应付需求改动写的。 384 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.0" 6 | defaultConfig { 7 | applicationId "com.gersion.multiselecter" 8 | minSdkVersion 16 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile project(':library') 28 | compile 'com.android.support:appcompat-v7:26.+' 29 | compile 'com.android.support:recyclerview-v7:26.+' 30 | compile 'com.github.bumptech.glide:glide:3.7.0' 31 | testCompile 'junit:junit:4.12' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidEnv\AndroidStudioSDK\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript inter 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.inter.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/gersion/multiselecter/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.gersion.multiselecter", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/GlideImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | import com.gersion.library.inter.IImageLoader; 8 | 9 | /** 10 | * Created by gersy on 2017/8/6. 11 | */ 12 | 13 | public class GlideImageLoader implements IImageLoader { 14 | @Override 15 | public void showImage(Context context, String url, ImageView imageView) { 16 | Glide.with(context) 17 | .load(url) 18 | .error(R.mipmap.ic_access_time_purple_200_24dp) 19 | .into(imageView); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/ImageUtil.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | 8 | public class ImageUtil { 9 | 10 | public static void loadRetangleImage(Context context, String url, ImageView imag) { 11 | Glide 12 | .with(context) 13 | .load(url) 14 | .placeholder(R.mipmap.ic_backup_purple_200_24dp) //设置占位图 15 | .error(R.mipmap.ic_archive_purple_200_24dp) //设置错误图片 16 | .crossFade(200) //设置淡入淡出效果,默认300ms,可以传参 17 | //.dontAnimate() //不显示动画效果 18 | .into(imag); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.widget.LinearLayout; 7 | 8 | import com.gersion.library.MultiSelecter; 9 | import com.gersion.library.inter.Filter; 10 | import com.gersion.library.view.MultiSelectView; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private RecyclerView mRecyclerView; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | // mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); 24 | // initRecycleView(); 25 | 26 | // FragmentManager supportFragmentManager = getSupportFragmentManager(); 27 | // FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction(); 28 | // SelectionFragment fragment = new SelectionFragment(); 29 | // MultiBeanListPool typePool = new MultiBeanListPool(); 30 | // typePool.register(UserBean.class,R.layout.item_user); 31 | // fragment.setTypePool(typePool); 32 | // fragmentTransaction.add(R.id.container, fragment); 33 | // fragmentTransaction.commit(); 34 | LinearLayout container = (LinearLayout) findViewById(R.id.container); 35 | // MultiSelectView multiSelectView = (MultiSelectView) findViewById(R.id.multiSelectView); 36 | // MultiSelecter.mImageLoader = new GlideImageLoader(); 37 | // MultiBeanListPool typePool = new MultiBeanListPool(); 38 | // typePool.register(UserBean.class,R.layout.item_user); 39 | // typePool.register(TitleBean.class,R.layout.item_title); 40 | // multiSelectView.setAdapter(new UserAdapter()); 41 | // multiSelectView.setTypePool(typePool); 42 | // multiSelectView.init(); 43 | // SelectionFragment selectionFragment = new SelectionFragment.Builder(this, new UserAdapter(), typePool).build(); 44 | MultiSelectView selectView = new MultiSelecter.Builder(this, container) 45 | .setImageLoader(new GlideImageLoader()) 46 | .setMultiAdapter(new UserAdapter()) 47 | .setSelectType(MultiSelecter.MULTI_SELECT) 48 | .register(UserBean.class, R.layout.item_user) 49 | .register(TitleBean.class, R.layout.item_title) 50 | .build(); 51 | List items = new ArrayList<>(); 52 | for (int i=0;i<100;i++){ 53 | if (i%13==0){ 54 | TitleBean titleBean = new TitleBean(); 55 | titleBean.title = "标题:"+i; 56 | items.add(titleBean); 57 | continue; 58 | } 59 | UserBean bean = new UserBean(); 60 | bean.age = i; 61 | bean.userName = "名称 "+i; 62 | // if (i%7==0){ 63 | // bean.icon = R.mipmap.ic_lock_purple_200_24dp; 64 | // }else if (i%5==0){ 65 | // bean.icon = R.mipmap.ic_access_time_purple_200_24dp; 66 | // }else if (i%3==0){ 67 | // bean.icon = R.mipmap.ic_archive_purple_200_24dp; 68 | // }else if (i%2==0){ 69 | // bean.icon = R.mipmap.ic_assignment_turned_in_purple_200_24dp; 70 | // }else { 71 | // bean.icon = R.mipmap.ic_backup_purple_200_24dp; 72 | // } 73 | 74 | if (i%7==0){ 75 | bean.iconUrl = "http://www.qq1234.org/uploads/allimg/150420/100A5G63-1.jpg"; 76 | }else if (i%5==0){ 77 | bean.iconUrl = "http://www.jdxcjs.com/cimg/bd13698490.jpg"; 78 | }else if (i%3==0){ 79 | bean.iconUrl = "http://qq1234.org/uploads/allimg/140504/3_140504160752_2.jpg"; 80 | }else if (i%2==0){ 81 | bean.iconUrl = "http://img3.imgtn.bdimg.com/it/u=2124504335,2887169219&fm=27&gp=0.jpg"; 82 | }else { 83 | bean.iconUrl = "http://www.qq1234.org/uploads/allimg/150420/100A52c6-0.jpg"; 84 | } 85 | items.add(bean); 86 | } 87 | selectView.handleData(items); 88 | } 89 | 90 | private void initRecycleView() { 91 | // LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); 92 | // linearLayoutManager.setOrientation(VERTICAL); 93 | // MultiTypeAdapter adapter = new MultiTypeAdapter<>(); 94 | // adapter.register(UserBean.class,new UserViewBinder()); 95 | // mRecyclerView.setLayoutManager(linearLayoutManager); 96 | // mRecyclerView.setAdapter(adapter); 97 | // List items = new ArrayList<>(); 98 | // for (int i=0;i<100;i++){ 99 | // UserBean bean = new UserBean(); 100 | // bean.age = i; 101 | // bean.userName = "名称 "+i; 102 | // items.add(bean); 103 | // } 104 | // adapter.setItems(items); 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/MultiSelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class MultiSelectActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_multi_select); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/SingleSelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class SingleSelectActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_single_select); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/TitleBean.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import com.gersion.library.inter.Filter; 4 | 5 | /** 6 | * Created by gersy on 2017/8/6. 7 | */ 8 | 9 | public class TitleBean implements Filter { 10 | public String title; 11 | 12 | @Override 13 | public int getImageResource() { 14 | return 0; 15 | } 16 | 17 | @Override 18 | public String getImageUrl() { 19 | return null; 20 | } 21 | 22 | @Override 23 | public int filter() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public boolean isSelected() { 29 | return false; 30 | } 31 | 32 | @Override 33 | public void setSelected(boolean isSelected) { 34 | 35 | } 36 | 37 | @Override 38 | public boolean isMatch(String condition) { 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import android.view.View; 4 | import android.widget.CheckBox; 5 | import android.widget.ImageView; 6 | 7 | import com.gersion.library.adapter.BaseMultiAdapter; 8 | import com.gersion.library.inter.Filter; 9 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 10 | 11 | /** 12 | * Created by gersy on 2017/8/2. 13 | */ 14 | 15 | public class UserAdapter extends BaseMultiAdapter { 16 | 17 | private CheckBox mCheckBox; 18 | 19 | @Override 20 | protected void convert(final BaseViewHolder helper, final Filter item) { 21 | if (item instanceof UserBean) { 22 | final UserBean bean = (UserBean) item; 23 | mCheckBox = (CheckBox) helper.getView(R.id.cb_selectmember); 24 | helper.setText(R.id.tv_name, bean.userName); 25 | // helper.setImageResource(R.id.iv_icon, bean.icon); 26 | final ImageView icon = (ImageView) helper.getView(R.id.iv_icon); 27 | // MultiSelecter.mImageLoader.showImage(mContext,item.getImageUrl(),icon); 28 | ImageUtil.loadRetangleImage(mContext,item.getImageUrl(),icon); 29 | helper.itemView.setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View view) { 32 | boolean checked = mCheckBox.isChecked(); 33 | mCheckBox.setChecked(!checked); 34 | bean.setSelected(!checked); 35 | if (mListener != null) { 36 | mListener.onItemClick(helper.itemView, bean, !checked); 37 | } 38 | } 39 | }); 40 | }else if (item instanceof TitleBean){ 41 | TitleBean bean = (TitleBean) item; 42 | helper.setText(R.id.tv_title,bean.title); 43 | } 44 | } 45 | 46 | @Override 47 | protected CheckBox getCheckBox() { 48 | return mCheckBox; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/gersion/multiselecter/UserBean.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import com.gersion.library.inter.Filter; 4 | import com.gersion.library.utils.MatchUtils; 5 | 6 | /** 7 | * Created by gersy on 2017/7/25. 8 | */ 9 | 10 | public class UserBean implements Filter { 11 | public String userName; 12 | public int icon; 13 | public int age; 14 | public boolean isSelected; 15 | public String iconUrl; 16 | 17 | @Override 18 | public boolean isSelected() { 19 | return isSelected; 20 | } 21 | 22 | @Override 23 | public void setSelected(boolean isSelected) { 24 | this.isSelected = isSelected; 25 | } 26 | 27 | @Override 28 | public int getImageResource() { 29 | return icon; 30 | } 31 | 32 | @Override 33 | public String getImageUrl() { 34 | return iconUrl; 35 | } 36 | 37 | @Override 38 | public int filter() { 39 | if (age<3){ 40 | return Filter.NO_CHOICE; 41 | }else if (age>=3&&age<100){ 42 | return Filter.NORMAL; 43 | } 44 | return 0; 45 | } 46 | 47 | @Override 48 | public boolean isMatch(String condition) { 49 | if (MatchUtils.isMatch(userName,condition)){ 50 | return true; 51 | } 52 | return false; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_multi_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_single_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 17 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 24 | 25 | 37 | 38 | 46 | 47 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_access_time_purple_200_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_access_time_purple_200_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_archive_purple_200_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_archive_purple_200_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_assignment_turned_in_purple_200_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_assignment_turned_in_purple_200_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_backup_purple_200_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_backup_purple_200_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_lock_purple_200_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xhdpi/ic_lock_purple_200_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/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/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MultiSelecter 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/test/java/com/gersion/multiselecter/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gersion.multiselecter; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | maven { url "https://jitpack.io" } 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 26 13:36:49 CST 2017 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-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | group='com.github.GaoGersy' 4 | android { 5 | compileSdkVersion 26 6 | buildToolsVersion "26.0.0" 7 | 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | testCompile 'junit:junit:4.12' 27 | compile 'com.android.support:support-annotations:25.3.1' 28 | compile 'com.android.support:recyclerview-v7:26.+' 29 | compile 'com.android.support:appcompat-v7:26.+' 30 | } 31 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidEnv\AndroidStudioSDK\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript inter 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.inter.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/gersion/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.gersion.library.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/MultiSelecter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | 6 | import com.gersion.library.adapter.BaseMultiAdapter; 7 | import com.gersion.library.inter.IImageLoader; 8 | import com.gersion.library.multitype.typepool.MultiBeanListPool; 9 | import com.gersion.library.multitype.typepool.MultiLayoutListPool; 10 | import com.gersion.library.multitype.typepool.TypePool; 11 | import com.gersion.library.view.MultiSelectView; 12 | 13 | /** 14 | * Created by gersy on 2017/8/2. 15 | */ 16 | 17 | public class MultiSelecter { 18 | //单选 19 | public static final int SINGLE_SELECT = 100; 20 | //多选 21 | public static final int MULTI_SELECT = 101; 22 | 23 | private TypePool mTypePool; 24 | private Context mContext; 25 | private BaseMultiAdapter mMultiAdapter; 26 | public static IImageLoader mImageLoader; 27 | private int mSelectType; 28 | private MultiSelectView mMultiSelectView; 29 | private ViewGroup mViewContainer; 30 | 31 | private MultiSelecter(Builder builder) { 32 | if (builder.mImageLoader == null) { 33 | throw new NullPointerException("ImageLoader 不能为空"); 34 | } 35 | if (builder.mMultiAdapter == null) { 36 | throw new NullPointerException("BaseMultiAdapter 不能为空"); 37 | } 38 | if (builder.mTypePool == null) { 39 | throw new NullPointerException("TypePool 不能为空"); 40 | } 41 | if (builder.mViewContainer ==null){ 42 | throw new NullPointerException("ViewContainer 不能为空"); 43 | } 44 | mContext = builder.mContext; 45 | mTypePool = builder.mTypePool; 46 | mMultiAdapter = builder.mMultiAdapter; 47 | mSelectType = builder.mSelectType; 48 | mViewContainer = builder.mViewContainer; 49 | mImageLoader = builder.mImageLoader; 50 | } 51 | 52 | private MultiSelectView build() { 53 | mMultiSelectView = new MultiSelectView(mContext); 54 | mMultiSelectView.setAdapter(this.mMultiAdapter); 55 | mMultiSelectView.setTypePool(mTypePool); 56 | mMultiSelectView.setSelectType(mSelectType); 57 | mMultiSelectView.init(); 58 | return into(mViewContainer); 59 | } 60 | 61 | private MultiSelectView into(ViewGroup container){ 62 | ViewGroup.LayoutParams params = 63 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 64 | ViewGroup.LayoutParams.MATCH_PARENT); 65 | container.addView(mMultiSelectView,params); 66 | return mMultiSelectView; 67 | } 68 | 69 | public static class Builder { 70 | private Context mContext; 71 | private ViewGroup mViewContainer; 72 | private BaseMultiAdapter mMultiAdapter; 73 | private TypePool mTypePool; 74 | private IImageLoader mImageLoader; 75 | private int mSelectType = MULTI_SELECT; 76 | 77 | public Builder(Context context,ViewGroup viewContainer) { 78 | mContext = context; 79 | mViewContainer = viewContainer; 80 | } 81 | 82 | public Builder setMultiAdapter(BaseMultiAdapter multiAdapter) { 83 | mMultiAdapter = multiAdapter; 84 | return this; 85 | } 86 | 87 | public Builder setTypePool(TypePool typePool) { 88 | mTypePool = typePool; 89 | return this; 90 | } 91 | 92 | public Builder setImageLoader(IImageLoader imageLoader) { 93 | mImageLoader = imageLoader; 94 | return this; 95 | } 96 | 97 | public Builder setSelectType(int selectType) { 98 | mSelectType = selectType; 99 | return this; 100 | } 101 | 102 | public Builder register(Class clazz, int layoutId) { 103 | if (mTypePool == null) { 104 | mTypePool = new MultiBeanListPool(); 105 | } 106 | if (mTypePool instanceof MultiLayoutListPool) { 107 | throw new IllegalStateException("已经注册过 MultiLayoutListPool 类型,不能再注册成MultiBeanListPool"); 108 | } 109 | mTypePool.register(clazz, layoutId); 110 | return this; 111 | } 112 | 113 | public Builder register(int layoutId) { 114 | if (mTypePool == null) { 115 | mTypePool = new MultiLayoutListPool(); 116 | } 117 | if (mTypePool instanceof MultiBeanListPool) { 118 | throw new IllegalStateException("已经注册过 MultiBeanListPool 类型,不能再注册成 MultiLayoutListPool"); 119 | } 120 | mTypePool.register(layoutId); 121 | return this; 122 | } 123 | 124 | public MultiSelectView build() { 125 | return new MultiSelecter(this).build(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/adapter/BaseMultiAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.widget.CheckBox; 5 | 6 | import com.gersion.library.inter.Filter; 7 | import com.gersion.library.multitype.adapter.MultiTypeAdapter; 8 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 9 | import com.gersion.library.view.smartrecycleview.IRVAdapter; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by gersy on 2017/7/25. 15 | */ 16 | 17 | public abstract class BaseMultiAdapter extends MultiTypeAdapter> implements IRVAdapter { 18 | 19 | @Override 20 | public RecyclerView.Adapter getAdapter() { 21 | return this; 22 | } 23 | 24 | public List getList() { 25 | return items; 26 | } 27 | 28 | public List getSelectionList() { 29 | return mSelectionList; 30 | } 31 | 32 | public void changeAllDataStatus(boolean selected) { 33 | for (Filter friendDataBean : items) { 34 | if (friendDataBean.filter()!=Filter.NO_CHOICE) { 35 | friendDataBean.setSelected(selected); 36 | } 37 | } 38 | notifyDataSetChanged(); 39 | } 40 | 41 | public void updateItem(T bean){ 42 | int index = -1; 43 | for (int i = 0; i < items.size(); i++) { 44 | T friendDataBean = items.get(i); 45 | if (friendDataBean==bean){ 46 | friendDataBean.setSelected(false); 47 | index = i; 48 | break; 49 | } 50 | } 51 | notifyItemChanged(index); 52 | } 53 | 54 | public T getItem(int position){ 55 | return items.get(position); 56 | } 57 | 58 | @Override 59 | public void setNewData(List data) { 60 | items = getFilterItems(data); 61 | notifyDataSetChanged(); 62 | } 63 | 64 | @Override 65 | public void addData(List data) { 66 | items.addAll(data); 67 | notifyDataSetChanged(); 68 | } 69 | 70 | @Override 71 | public void removeAll(List data) { 72 | items.removeAll(data); 73 | notifyDataSetChanged(); 74 | } 75 | 76 | @Override 77 | public void remove(Filter data) { 78 | items.remove(data); 79 | notifyDataSetChanged(); 80 | } 81 | 82 | @Override 83 | public List getData() { 84 | return items; 85 | } 86 | 87 | @Override 88 | protected abstract void convert(BaseViewHolder helper, T item); 89 | 90 | @Override 91 | protected abstract CheckBox getCheckBox(); 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/adapter/SelectIconRvAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | 11 | import com.gersion.library.MultiSelecter; 12 | import com.gersion.library.R; 13 | import com.gersion.library.inter.Filter; 14 | import com.gersion.library.inter.OnItemClickListener; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class SelectIconRvAdapter extends RecyclerView.Adapter { 20 | 21 | private List itemFilters = new ArrayList<>(); 22 | private OnItemClickListener mListener; 23 | private static Context mContext; 24 | 25 | 26 | public void setItemFilters(@NonNull List itemFilters) { 27 | this.itemFilters = itemFilters; 28 | } 29 | 30 | public int add(Filter bean) { 31 | itemFilters.add(bean); 32 | int i = itemFilters.size() - 1; 33 | notifyItemChanged(i); 34 | return i; 35 | } 36 | 37 | public void remove(Filter bean) { 38 | itemFilters.remove(bean); 39 | notifyDataSetChanged(); 40 | } 41 | 42 | public void remove(int position) { 43 | itemFilters.remove(position); 44 | notifyItemChanged(position); 45 | } 46 | 47 | public void addAllData(List data) { 48 | itemFilters.clear(); 49 | itemFilters.addAll(data); 50 | notifyDataSetChanged(); 51 | } 52 | 53 | public void addAll(List data) { 54 | itemFilters.addAll(data); 55 | notifyDataSetChanged(); 56 | } 57 | 58 | public void clear() { 59 | itemFilters.clear(); 60 | notifyDataSetChanged(); 61 | } 62 | 63 | public void setData(List data) { 64 | itemFilters = data; 65 | notifyDataSetChanged(); 66 | } 67 | 68 | 69 | public void addHiddenItem(Filter bean) { 70 | itemFilters.add(bean); 71 | int i = itemFilters.size() - 1; 72 | notifyItemChanged(i); 73 | } 74 | 75 | public Filter getItem(int position) { 76 | return itemFilters.get(position); 77 | } 78 | 79 | public List getList() { 80 | return itemFilters; 81 | } 82 | 83 | 84 | @Override 85 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 86 | mContext = parent.getContext(); 87 | View view = LayoutInflater.from(mContext) 88 | .inflate(R.layout.item_icon, parent, false); 89 | return new ViewHolder(view); 90 | } 91 | 92 | 93 | @Override 94 | public void onBindViewHolder(ViewHolder holder, int position) { 95 | Filter itemFilter = itemFilters.get(position); 96 | holder.setOnItemClickListener(mListener); 97 | holder.setData(itemFilter, position); 98 | } 99 | 100 | 101 | @Override 102 | public int getItemCount() { 103 | return itemFilters.size(); 104 | } 105 | 106 | 107 | public static class ViewHolder extends RecyclerView.ViewHolder { 108 | 109 | ImageView cover; 110 | private OnItemClickListener mListener; 111 | 112 | ViewHolder(View itemView) { 113 | super(itemView); 114 | cover = itemView.findViewById(R.id.icon); 115 | } 116 | 117 | public void setData(final Filter data, int position) { 118 | itemView.setOnClickListener(new View.OnClickListener() { 119 | @Override 120 | public void onClick(View v) { 121 | if (mListener != null) { 122 | mListener.onItemClick(v, data, false); 123 | } 124 | } 125 | }); 126 | MultiSelecter.mImageLoader.showImage(mContext,data.getImageUrl(),cover); 127 | 128 | } 129 | 130 | public View getItemView() { 131 | return itemView; 132 | } 133 | 134 | public void setOnItemClickListener(OnItemClickListener listener) { 135 | mListener = listener; 136 | } 137 | } 138 | 139 | public void setOnItemClickListener(OnItemClickListener listener) { 140 | mListener = listener; 141 | } 142 | } -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/bean/FloatImgBean.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.bean; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by gersy on 2017/8/3. 7 | */ 8 | 9 | public class FloatImgBean { 10 | public ImageView mImageView; 11 | public boolean mIsAnimator; 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/fragment/SelectionFragment.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.fragment; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.os.Bundle; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.app.FragmentActivity; 12 | import android.support.v4.app.FragmentManager; 13 | import android.support.v4.app.FragmentTransaction; 14 | import android.support.v7.widget.LinearLayoutManager; 15 | import android.support.v7.widget.RecyclerView; 16 | import android.text.Editable; 17 | import android.text.TextWatcher; 18 | import android.util.Log; 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.view.animation.OvershootInterpolator; 23 | import android.widget.EditText; 24 | import android.widget.FrameLayout; 25 | import android.widget.ImageView; 26 | import android.widget.LinearLayout; 27 | import android.widget.TextView; 28 | 29 | import com.gersion.library.MultiSelecter; 30 | import com.gersion.library.R; 31 | import com.gersion.library.adapter.BaseMultiAdapter; 32 | import com.gersion.library.adapter.SelectIconRvAdapter; 33 | import com.gersion.library.bean.FloatImgBean; 34 | import com.gersion.library.inter.Filter; 35 | import com.gersion.library.inter.OnItemClickListener; 36 | import com.gersion.library.multitype.typepool.TypePool; 37 | import com.gersion.library.utils.ScreenUtils; 38 | import com.gersion.library.utils.SizeUtils; 39 | import com.gersion.library.view.smartrecycleview.SmartRecycleView; 40 | 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | 44 | /** 45 | * Created by gersy on 2017/7/25. 46 | */ 47 | 48 | public class SelectionFragment extends Fragment { 49 | //单选 50 | public static final int SINGLE_SELECT = 100; 51 | //多选 52 | public static final int MULTI_SELECT = 101; 53 | 54 | private Activity activity; 55 | private View mView; 56 | private LinearLayout mContainer; 57 | private RecyclerView mIconRecyclerView; 58 | private EditText mEtSearch; 59 | private SmartRecycleView mSmartRecycleView; 60 | private LinearLayout mLlSelectAll; 61 | private ImageView mIvSelectAll; 62 | private SelectIconRvAdapter mIconListRvAdapter; 63 | private LinearLayoutManager mLinearLayoutManager; 64 | private List mSelectList; 65 | private TextView mTvConfirm; 66 | private int mMaxWidth; 67 | private BaseMultiAdapter mAdapter; 68 | private List mData; 69 | private ImageView mFloatImg_1; 70 | private int mStartX; 71 | private int mStartY; 72 | private FrameLayout mFlContainer; 73 | private View mSourceView; 74 | private TypePool mTypePool; 75 | private ImageView mFloatImg_2; 76 | private ImageView mFloatImg_3; 77 | private List mImagePool = new ArrayList<>(); 78 | private List mMatchList = new ArrayList<>(); 79 | private View mPlaceHolder; 80 | private int selectType = SelectionFragment.MULTI_SELECT; 81 | 82 | @Override 83 | public void onAttach(Context context) { 84 | super.onAttach(context); 85 | this.activity = (Activity) context; 86 | } 87 | 88 | @Override 89 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 90 | mView = inflater.inflate(R.layout.fragment_selection, container, false); 91 | initView(); 92 | initListener(); 93 | return mView; 94 | } 95 | 96 | protected T findView(int id) { 97 | return (T) mView.findViewById(id); 98 | } 99 | 100 | private void initView() { 101 | mContainer = findView(R.id.container); 102 | mIconRecyclerView = findView(R.id.icon_recyclerView); 103 | mEtSearch = findView(R.id.et_search); 104 | mSmartRecycleView = findView(R.id.smartRecycleView); 105 | mLlSelectAll = findView(R.id.ll_select_all); 106 | mIvSelectAll = findView(R.id.iv_select_all); 107 | mTvConfirm = findView(R.id.tv_confirm); 108 | mFloatImg_1 = findView(R.id.float_img_1); 109 | mFloatImg_2 = findView(R.id.float_img_2); 110 | mFloatImg_3 = findView(R.id.float_img_3); 111 | mFlContainer = findView(R.id.fl_container); 112 | mPlaceHolder = findView(R.id.placeholder); 113 | 114 | // int[] location = new int[2]; 115 | // mContainer.getLocationOnScreen(location); 116 | // mStartX = location[0]; 117 | // mStartY = location[1]; 118 | FloatImgBean bean1 = new FloatImgBean(); 119 | bean1.mImageView = mFloatImg_1; 120 | FloatImgBean bean2 = new FloatImgBean(); 121 | bean2.mImageView = mFloatImg_2; 122 | FloatImgBean bean3 = new FloatImgBean(); 123 | bean3.mImageView = mFloatImg_3; 124 | mImagePool.add(bean1); 125 | mImagePool.add(bean2); 126 | mImagePool.add(bean3); 127 | 128 | int screenWidth = ScreenUtils.getScreenWidth(activity); 129 | mMaxWidth = screenWidth * 2 / 3; 130 | initRecyclerView(); 131 | } 132 | 133 | private FloatImgBean getFloatImg() { 134 | for (FloatImgBean bean : mImagePool) { 135 | if (!bean.mIsAnimator) { 136 | return bean; 137 | } 138 | } 139 | return null; 140 | } 141 | 142 | private void initRecyclerView() { 143 | mIconListRvAdapter = new SelectIconRvAdapter(); 144 | mLinearLayoutManager = new LinearLayoutManager(activity); 145 | mLinearLayoutManager.setAutoMeasureEnabled(true); 146 | 147 | mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); 148 | mIconRecyclerView.setLayoutManager(mLinearLayoutManager); 149 | mIconRecyclerView.setAdapter(mIconListRvAdapter); 150 | 151 | mSelectList = mIconListRvAdapter.getList(); 152 | 153 | mAdapter.setTypePool(mTypePool); 154 | mSmartRecycleView.setFirstPage(1) 155 | .setAutoRefresh(false) 156 | .setPageSize(20) 157 | .setAdapter(mAdapter) 158 | .loadMoreEnable(false) 159 | .refreshEnable(false) 160 | .setLayoutManger(SmartRecycleView.LayoutManagerType.LINEAR_LAYOUT); 161 | 162 | mSmartRecycleView.handleData(mData); 163 | 164 | } 165 | 166 | public void setTypePool(TypePool typePool) { 167 | this.mTypePool = typePool; 168 | } 169 | 170 | public void setAdapter(BaseMultiAdapter adapter) { 171 | this.mAdapter = adapter; 172 | } 173 | 174 | private void getParentPoint() { 175 | int[] locations = new int[2]; 176 | mIconRecyclerView.getLocationOnScreen(locations); 177 | mStartX = locations[0]; 178 | mStartY = locations[1]; 179 | } 180 | 181 | int count = 0; 182 | 183 | public void getSourcePoint(final View itemView, final Filter item) { 184 | getParentPoint(); 185 | itemView.setClickable(false); 186 | final FloatImgBean floatImg = getFloatImg(); 187 | floatImg.mImageView.setVisibility(View.VISIBLE); 188 | mPlaceHolder.setVisibility(View.VISIBLE); 189 | floatImg.mImageView.setImageResource(item.getImageResource()); 190 | MultiSelecter.mImageLoader.showImage(getActivity(),item.getImageUrl(),floatImg.mImageView); 191 | floatImg.mIsAnimator = true; 192 | int[] targetCoordinates = getTarget(mIconRecyclerView, count++); 193 | Log.d("haha", "调用结果 x = " + targetCoordinates[0] + ", y = " + targetCoordinates[1]); 194 | 195 | int[] sourceLocation = new int[2]; 196 | mSourceView.getLocationOnScreen(sourceLocation); 197 | int startX = sourceLocation[0]; 198 | int startY = sourceLocation[1]; 199 | 200 | // ViewGroup parent = (ViewGroup) mSourceView.getParent(); 201 | // parent.removeView(mSourceView); 202 | // mFlContainer.addView(mSourceView); 203 | 204 | int[] tagetLocation = new int[2]; 205 | mIconRecyclerView.getLocationOnScreen(tagetLocation); 206 | int endX = tagetLocation[0] + mIconRecyclerView.getWidth() + SizeUtils.dp2px(activity, 5); 207 | int endY = tagetLocation[1] + SizeUtils.dp2px(activity, 5) * 2; 208 | // if (endX >= mMaxWidth) { 209 | // endX = mMaxWidth - SizeUtils.dp2px(activity, 40); 210 | // } 211 | 212 | ObjectAnimator animatorX = ObjectAnimator.ofFloat(floatImg.mImageView, "translationX", startX - mStartX, endX - mStartX); 213 | ObjectAnimator animatorY = ObjectAnimator.ofFloat(floatImg.mImageView, "translationY", startY - mStartY, endY - mStartY); 214 | AnimatorSet animatorSet = new AnimatorSet(); 215 | animatorSet.playTogether(animatorX, animatorY); 216 | animatorSet.setDuration(calcDuration(startX - endX, startY - endY)); 217 | animatorSet.setInterpolator(new OvershootInterpolator(1.1f)); 218 | animatorSet.start(); 219 | animatorSet.addListener(new AnimatorListenerAdapter() { 220 | @Override 221 | public void onAnimationEnd(Animator animation) { 222 | floatImg.mImageView.setVisibility(View.GONE); 223 | floatImg.mIsAnimator = false; 224 | // mIconListRvAdapter.showLastItem(); 225 | mIconListRvAdapter.add(item); 226 | mIconRecyclerView.smoothScrollToPosition(mIconListRvAdapter.getItemCount()); 227 | mPlaceHolder.setVisibility(View.GONE); 228 | refreshLayout(false); 229 | itemView.setClickable(true); 230 | } 231 | }); 232 | } 233 | 234 | private void animate(RecyclerView sourceRecycler, RecyclerView targetRecycler, int position) { 235 | View view = sourceRecycler.getLayoutManager().findViewByPosition(position); 236 | if (view == null) { 237 | return; 238 | } 239 | getParentPoint(); 240 | 241 | mFloatImg_1.setPivotX(mFloatImg_1.getWidth() / 2); 242 | mFloatImg_1.setPivotY(mFloatImg_1.getHeight() / 2); 243 | mFloatImg_1.setVisibility(View.VISIBLE); 244 | int[] initial = new int[2]; 245 | view.getLocationOnScreen(initial); 246 | 247 | sourceRecycler.getLayoutManager().removeViewAt(position); 248 | 249 | BaseMultiAdapter sourceRecyclerAdapter = (BaseMultiAdapter) sourceRecycler.getAdapter(); 250 | Filter removedItem = sourceRecyclerAdapter.getItem(position); 251 | 252 | int width = view.getWidth(); 253 | // view.removeFromParent(); 254 | // parent.addView(view) 255 | // view.layoutParams = view.layoutParams.apply { 256 | // this.width = width 257 | // } 258 | int[] container = new int[2]; 259 | sourceRecycler.getLocationOnScreen(container); 260 | 261 | view.setTranslationX(initial[0] + 0.5f); 262 | view.setTranslationY((initial[1] - container[1]) + 0.5f); 263 | 264 | // @Suppress("UNCHECKED_CAST") 265 | SelectIconRvAdapter adapter = (SelectIconRvAdapter) targetRecycler.getAdapter(); 266 | int newPos = adapter.add(removedItem); 267 | int[] targetCoordinates = getTarget(targetRecycler, newPos); 268 | 269 | 270 | float targetX = (targetCoordinates[0] - initial[0]) + 0.5f; 271 | float targetY = (targetCoordinates[1] - initial[1]) + 0.5f; 272 | long duration = calcDuration(targetX, targetY); 273 | 274 | animateTranslation(mFloatImg_1, targetX, targetY, duration); 275 | 276 | // ObjectAnimator animatorX = ObjectAnimator.ofFloat(mFloatImg_1, "translationX", initial[0], targetCoordinates[0]); 277 | // ObjectAnimator animatorY = ObjectAnimator.ofFloat(mFloatImg_1, "translationY", initial[1] - mStartY, targetCoordinates[1] - mStartY); 278 | // AnimatorSet animatorSet = new AnimatorSet(); 279 | // animatorSet.playTogether(animatorX, animatorY); 280 | // animatorSet.setDuration(500); 281 | // animatorSet.start(); 282 | 283 | // animateAlpha(removedItem, targetRecycler, view, duration) 284 | // animateTranslation(view, deltaX = targetX, deltaY = targetY, duration = duration) 285 | } 286 | 287 | private void animateTranslation(View view, float deltaX, float deltaY, Long duration) { 288 | view.animate().setDuration(duration) 289 | .setInterpolator(new OvershootInterpolator(1.1f)) 290 | .translationXBy(deltaX) 291 | .translationYBy(deltaY) 292 | .start(); 293 | } 294 | 295 | private long calcDuration(float targetX, float targetY) { 296 | return (long) (Math.sqrt((targetX * targetX + targetY * targetY)) * 0.7f); 297 | } 298 | 299 | private int[] getTarget(RecyclerView targetRecycler, int index) { 300 | int prev = Math.max(0, index - 0); 301 | RecyclerView.ViewHolder viewHolderForAdapterPosition = targetRecycler.findViewHolderForAdapterPosition(prev); 302 | View targetView = viewHolderForAdapterPosition == null ? null : viewHolderForAdapterPosition.itemView; 303 | if (targetView == null) { 304 | viewHolderForAdapterPosition = targetRecycler.findViewHolderForAdapterPosition(prev - 1); 305 | targetView = viewHolderForAdapterPosition == null ? null : viewHolderForAdapterPosition.itemView; 306 | if (targetView != null) { 307 | int[] targetCoordinates = new int[2]; 308 | targetView.getLocationOnScreen(targetCoordinates); 309 | targetCoordinates[1] += targetView.getHeight(); 310 | Log.d("haha", "targetView !=null x = " + targetCoordinates[0] + ", y = " + targetCoordinates[1]); 311 | return targetCoordinates; 312 | } 313 | } 314 | 315 | if (targetView == null) { 316 | int[] targetCoordinates = new int[2]; 317 | targetRecycler.getLocationOnScreen(targetCoordinates); 318 | if (targetRecycler.getChildCount() != 0) { 319 | targetCoordinates[1] += targetRecycler.getHeight(); 320 | } 321 | Log.d("haha", "targetView == null x = " + targetCoordinates[0] + ", y = " + targetCoordinates[1]); 322 | return targetCoordinates; 323 | } 324 | 325 | return new int[]{0, 0}; 326 | } 327 | 328 | 329 | public void handleData(List data) { 330 | mData = data; 331 | if (mSmartRecycleView == null) { 332 | return; 333 | } 334 | mSmartRecycleView.handleData(data); 335 | } 336 | 337 | private void initListener() { 338 | mLlSelectAll.setOnClickListener(new View.OnClickListener() { 339 | @Override 340 | public void onClick(View v) { 341 | if (!mIvSelectAll.isSelected()) { 342 | List selectionList = mAdapter.getSelectionList(); 343 | mIconListRvAdapter.addAllData(selectionList); 344 | } else { 345 | mIconListRvAdapter.clear(); 346 | } 347 | mAdapter.changeAllDataStatus(!mIvSelectAll.isSelected()); 348 | refreshLayout(!mIvSelectAll.isSelected()); 349 | } 350 | }); 351 | 352 | mAdapter.setOnItemClickListener(new OnItemClickListener() { 353 | @Override 354 | public void onItemClick(View v, Filter item, boolean isSelected) { 355 | if (selectType == MULTI_SELECT) { 356 | if (isSelected) { 357 | mSourceView = v.findViewById(R.id.iv_icon); 358 | getSourcePoint(v, item); 359 | 360 | } else { 361 | mIconListRvAdapter.remove(item); 362 | } 363 | } else { 364 | if (isSelected) { 365 | mIconListRvAdapter.addHiddenItem(item); 366 | mIconRecyclerView.smoothScrollToPosition(mIconListRvAdapter.getItemCount()); 367 | } else { 368 | mIconListRvAdapter.remove(item); 369 | } 370 | } 371 | refreshLayout(false); 372 | } 373 | }); 374 | 375 | mIconListRvAdapter.setOnItemClickListener(new OnItemClickListener() { 376 | @Override 377 | public void onItemClick(View v, Filter item, boolean isSelected) { 378 | mAdapter.updateItem(item); 379 | mIconListRvAdapter.remove(item); 380 | refreshLayout(false); 381 | } 382 | }); 383 | 384 | mEtSearch.addTextChangedListener(new TextWatcher() { 385 | @Override 386 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 387 | 388 | } 389 | 390 | @Override 391 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 392 | 393 | } 394 | 395 | @Override 396 | public void afterTextChanged(Editable editable) { 397 | String trim = mEtSearch.getText().toString().trim(); 398 | if (trim.length() == 0) { 399 | mAdapter.setItems(mData); 400 | } else { 401 | mMatchList.clear(); 402 | for (Filter item : mData) { 403 | if (item.isMatch(trim)) { 404 | mMatchList.add(item); 405 | } 406 | } 407 | mAdapter.setItems(mMatchList); 408 | } 409 | } 410 | }); 411 | } 412 | 413 | private void refreshLayout(boolean isSelected) { 414 | mIvSelectAll.setSelected(isSelected); 415 | 416 | if (mAdapter.getSelectionList().size() == mIconListRvAdapter.getItemCount()) { 417 | mIvSelectAll.setSelected(true); 418 | } 419 | 420 | int size = mSelectList.size(); 421 | mTvConfirm.setText(size == 0 ? "确定" : "确定(" + size + ")"); 422 | if (size == 0) { 423 | mTvConfirm.setEnabled(false); 424 | } else { 425 | mTvConfirm.setEnabled(true); 426 | } 427 | int width = SizeUtils.dp2px(activity, 45) * size; 428 | if (width > mMaxWidth) { 429 | width = mMaxWidth; 430 | } 431 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.WRAP_CONTENT); 432 | mIconRecyclerView.setLayoutParams(params); 433 | mContainer.requestLayout(); 434 | } 435 | 436 | public static class Builder { 437 | private FragmentActivity mActivity; 438 | private BaseMultiAdapter mMultiAdapter; 439 | private TypePool mTypePool; 440 | 441 | public Builder(FragmentActivity activity, BaseMultiAdapter multiAdapter, TypePool typePool) { 442 | mActivity = activity; 443 | mMultiAdapter = multiAdapter; 444 | mTypePool = typePool; 445 | } 446 | 447 | public SelectionFragment build() { 448 | SelectionFragment selectionFragment = new SelectionFragment(); 449 | FragmentManager supportFragmentManager = mActivity.getSupportFragmentManager(); 450 | FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction(); 451 | fragmentTransaction.add(R.id.container, selectionFragment); 452 | fragmentTransaction.commit(); 453 | selectionFragment.setTypePool(mTypePool); 454 | selectionFragment.setAdapter(mMultiAdapter); 455 | return selectionFragment; 456 | } 457 | } 458 | 459 | } 460 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/Filter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | /** 4 | * Created by gersy on 2017/7/24. 5 | */ 6 | 7 | public interface Filter { 8 | 9 | //专为标题而生,因为系统默认给的是0,这样title Bean类里面都不用做过多的修改了 10 | int TITLE_NO_CHOICE = 0; 11 | 12 | //默认不选中,可以选中 13 | int NORMAL = 1; 14 | 15 | //默认选中,不可以取消选中 16 | int SELECTED_NOCANCEL = 2; 17 | 18 | //默认不选中,不能被选中 19 | int NO_CHOICE = 3; 20 | 21 | //不显示在列表 22 | int NOT_SHOW = 4; 23 | 24 | //本地图片地址 25 | int getImageResource(); 26 | 27 | //网络图片url 28 | String getImageUrl(); 29 | 30 | //返回当前条目的状态,就是上面定义的那些个常量,返回值会在BaseViewHolder里面用到 31 | int filter(); 32 | 33 | //是否是选中状态 34 | boolean isSelected(); 35 | 36 | void setSelected(boolean isSelected); 37 | 38 | //是否匹配搜索关键字,用来处理搜索的,如果不要搜索功能,可以不用处理 39 | boolean isMatch(String condition); 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/IImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | /** 7 | * Created by gersy on 2017/5/28. 8 | */ 9 | 10 | public interface IImageLoader { 11 | void showImage(Context context, String url, ImageView imageView); 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/IMatch.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | /** 4 | * Created by a3266 on 2017/5/29. 5 | */ 6 | 7 | public interface IMatch { 8 | boolean isMatch(String condition); 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/ItemFilter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | /** 4 | * Created by gersy on 2017/7/25. 5 | */ 6 | 7 | public interface ItemFilter extends Filter { 8 | 9 | String getIcon(); 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/OnItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | 4 | import android.view.View; 5 | 6 | /** 7 | * Created by gersy on 2017/7/25. 8 | */ 9 | 10 | public interface OnItemClickListener { 11 | void onItemClick(View v, Filter item, boolean isSelected); 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/OnLastItemLoadListener.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | 4 | import android.view.View; 5 | 6 | /** 7 | * Created by gersy on 2017/7/25. 8 | */ 9 | 10 | public interface OnLastItemLoadListener { 11 | void onLastLoaded(View v); 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/inter/OnSelectItemChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.inter; 2 | 3 | /** 4 | * Created by gersy on 2017/7/25. 5 | */ 6 | 7 | public interface OnSelectItemChangeListener { 8 | void onAdd(ItemFilter bean); 9 | void onRemove(ItemFilter bean); 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/adapter/MultiTypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.adapter; 18 | 19 | import android.content.Context; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | import android.support.v7.widget.RecyclerView.ViewHolder; 23 | import android.view.ViewGroup; 24 | import android.widget.CheckBox; 25 | 26 | import com.gersion.library.inter.Filter; 27 | import com.gersion.library.inter.OnItemClickListener; 28 | import com.gersion.library.multitype.inter.IMultiLayout; 29 | import com.gersion.library.multitype.typepool.MultiBeanListPool; 30 | import com.gersion.library.multitype.typepool.MultiLayoutListPool; 31 | import com.gersion.library.multitype.typepool.TypePool; 32 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 33 | 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | /** 38 | * @author drakeet 39 | */ 40 | public abstract class MultiTypeAdapter extends RecyclerView.Adapter { 41 | 42 | private static final String TAG = "MultiTypeAdapter"; 43 | protected OnItemClickListener mListener; 44 | protected List items; 45 | private TypePool typePool; 46 | protected Context mContext; 47 | 48 | //用来判断是使用了哪种注册类型< -1-还没使用过,0-多布局 ,1- 多Bean> 49 | private int registerType = -1; 50 | 51 | private static final int MULTI_LAYOUT = 0; 52 | private static final int MULTI_BEAN = 1; 53 | 54 | protected List mSelectionList = new ArrayList<>(); 55 | private int mSelectType; 56 | 57 | public MultiTypeAdapter() { 58 | this(new ArrayList()); 59 | } 60 | 61 | 62 | public MultiTypeAdapter(@NonNull List items) { 63 | this(items, null); 64 | } 65 | 66 | 67 | public MultiTypeAdapter(@NonNull List items, @NonNull TypePool pool) { 68 | this.items = getFilterItems(items); 69 | this.typePool = pool; 70 | } 71 | 72 | //注册多个bean 对应多个或单个布局 73 | public void registerMultiBean(Class clazz, int layoutId) { 74 | if (registerType == MULTI_LAYOUT) { 75 | throw new IllegalStateException("您已经使用过registerMultiLayout()方法,不能再使用registerMultiBean()注册,二者互斥"); 76 | } 77 | registerType = MULTI_BEAN; 78 | if (typePool == null) { 79 | typePool = new MultiBeanListPool(); 80 | } 81 | typePool.register(clazz, layoutId); 82 | } 83 | 84 | //注册一个bean对应多个布局 85 | public void registerMultiLayout(int layoutId) { 86 | if (registerType == MULTI_BEAN) { 87 | throw new IllegalStateException("您已经使用过registerMultiBean()方法,不能再使用registerMultiLayout()注册,二者互斥"); 88 | } 89 | registerType = MULTI_LAYOUT; 90 | if (typePool == null) { 91 | typePool = new MultiLayoutListPool(); 92 | } 93 | typePool.register(layoutId); 94 | } 95 | 96 | public void setItems(@NonNull List items) { 97 | this.items = getFilterItems(items); 98 | notifyDataSetChanged(); 99 | } 100 | 101 | @NonNull 102 | public List getItems() { 103 | return items; 104 | } 105 | 106 | 107 | public void setTypePool(@NonNull TypePool typePool) { 108 | this.typePool = typePool; 109 | } 110 | 111 | 112 | @NonNull 113 | public TypePool getTypePool() { 114 | return typePool; 115 | } 116 | 117 | public void setSelectType(int selectType){ 118 | mSelectType = selectType; 119 | } 120 | 121 | @Override 122 | public final int getItemViewType(int position) { 123 | int itemType = -1; 124 | if (registerType == MULTI_LAYOUT) { 125 | IMultiLayout item = (IMultiLayout) items.get(position); 126 | int layoutId = item.getLayoutId(); 127 | itemType = typePool.getItemType(layoutId); 128 | } else { 129 | Object item = items.get(position); 130 | itemType = typePool.getItemType(item.getClass()); 131 | } 132 | return itemType; 133 | } 134 | 135 | 136 | @Override 137 | public final ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) { 138 | if (mContext==null) { 139 | mContext = parent.getContext(); 140 | } 141 | ViewHolder viewHolder = typePool.getViewHolder(parent, indexViewType); 142 | return viewHolder; 143 | } 144 | 145 | @Override 146 | public final void onBindViewHolder(ViewHolder holder, int position) { 147 | BaseViewHolder baseViewHolder = (BaseViewHolder) holder; 148 | baseViewHolder.setAdapter(this); 149 | convert(baseViewHolder, items.get(position)); 150 | baseViewHolder.setCheckBox(getCheckBox()); 151 | baseViewHolder.setData(items.get(position)); 152 | baseViewHolder.setOnItemClickListener(mListener); 153 | baseViewHolder.setSelectType(mSelectType); 154 | } 155 | 156 | protected List getFilterItems(List items) { 157 | mSelectionList.clear(); 158 | if (items != null) { 159 | List data = new ArrayList<>(); 160 | for (T item : items) { 161 | int type = item.filter(); 162 | if (type != Filter.NOT_SHOW) { 163 | data.add(item); 164 | if (type != Filter.NO_CHOICE&&type!=Filter.TITLE_NO_CHOICE) { 165 | mSelectionList.add(item); 166 | } 167 | } 168 | } 169 | return data; 170 | } else { 171 | return null; 172 | } 173 | } 174 | 175 | protected abstract void convert(BaseViewHolder helper, T item); 176 | 177 | protected abstract CheckBox getCheckBox(); 178 | 179 | @Override 180 | public final int getItemCount() { 181 | return items.size(); 182 | } 183 | 184 | public void setOnItemClickListener(OnItemClickListener listener) { 185 | mListener = listener; 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/inter/IMultiLayout.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.multitype.inter; 2 | 3 | /** 4 | * Created by gersy on 2017/7/27. 5 | */ 6 | 7 | public interface IMultiLayout { 8 | int getLayoutId(); 9 | void setLayoutId(int layoutId); 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/typepool/MultiBeanListPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.typepool; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * An List implementation of TypePool. 31 | * 32 | * @author drakeet 33 | */ 34 | public class MultiBeanListPool implements TypePool { 35 | 36 | private List mTypes = new ArrayList<>(); 37 | 38 | public void register(Class clazz, int layoutId) { 39 | if (checkIsRegistered(clazz)){ 40 | throw new IllegalStateException(clazz + " 已经注册过,请不要重复注册"); 41 | } 42 | Type type = new Type(); 43 | type.layoutId = layoutId; 44 | type.itemType = mTypes.size(); 45 | type.clazz = clazz; 46 | mTypes.add(type); 47 | } 48 | 49 | @Override 50 | public void register(int layoutId) { 51 | 52 | } 53 | 54 | private boolean checkIsRegistered(Class clazz) { 55 | for (Type type : mTypes) { 56 | if (type.clazz == clazz){ 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int itemType) { 64 | Type type = mTypes.get(itemType); 65 | return type.getViewHolder(parent); 66 | } 67 | 68 | public int getItemType(Class clazz) { 69 | for (int i = 0; i < mTypes.size(); i++) { 70 | Type type = mTypes.get(i); 71 | if (type.clazz==clazz){ 72 | return i; 73 | } 74 | } 75 | throw new IllegalStateException(clazz+" 没有注册,请检查.."); 76 | } 77 | 78 | @Override 79 | public int getItemType(int layoutId) { 80 | return 0; 81 | } 82 | 83 | 84 | public static class Type { 85 | int itemType; 86 | int layoutId; 87 | Class clazz; 88 | private LayoutInflater inflater; 89 | 90 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent) { 91 | if (inflater == null) { 92 | inflater = LayoutInflater.from(parent.getContext()); 93 | } 94 | View view = inflater.inflate(layoutId, parent, false); 95 | return new BaseViewHolder(view); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/typepool/MultiLayoutListPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.typepool; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * An List implementation of TypePool. 31 | * 32 | * @author drakeet 33 | */ 34 | public class MultiLayoutListPool implements TypePool { 35 | 36 | private List mTypes = new ArrayList<>(); 37 | 38 | public void register(int layoutId) { 39 | if (checkIsRegistered(layoutId)) { 40 | throw new IllegalStateException("layoutId = "+layoutId + " 已经注册过,请不要重复注册"); 41 | } 42 | Type type = new Type(); 43 | type.layoutId = layoutId; 44 | type.itemType = mTypes.size(); 45 | mTypes.add(type); 46 | } 47 | 48 | private boolean checkIsRegistered(int layoutId) { 49 | for (Type type : mTypes) { 50 | if (type.layoutId == layoutId) { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | @Override 58 | public void register(Class clazz, int layouId) { 59 | 60 | } 61 | 62 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int itemType) { 63 | Type type = mTypes.get(itemType); 64 | return type.getViewHolder(parent); 65 | } 66 | 67 | public int getItemType(Class clazz) { 68 | return 0; 69 | } 70 | 71 | @Override 72 | public int getItemType(int layoutId) { 73 | for (int i = 0; i < mTypes.size(); i++) { 74 | Type type = mTypes.get(i); 75 | if (type.layoutId == layoutId) { 76 | return i; 77 | } 78 | } 79 | throw new IllegalStateException("layoutId = "+layoutId + " 没有注册,请检查.."); 80 | } 81 | 82 | 83 | public static class Type { 84 | int itemType; 85 | int layoutId; 86 | private LayoutInflater inflater; 87 | 88 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent) { 89 | if (inflater == null) { 90 | inflater = LayoutInflater.from(parent.getContext()); 91 | } 92 | View view = inflater.inflate(layoutId, parent, false); 93 | return new BaseViewHolder(view); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/typepool/MultiTypePoolList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.typepool; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * An List implementation of TypePool. 31 | * 32 | * @author drakeet 33 | */ 34 | public class MultiTypePoolList implements TypePool { 35 | 36 | private List mTypes = new ArrayList<>(); 37 | 38 | public void register(Class clazz, int layoutId) { 39 | if (checkIsRegistered(clazz)){ 40 | throw new IllegalStateException(clazz + " 已经注册过,请不要重复注册"); 41 | } 42 | Type type = new Type(); 43 | type.layoutId = layoutId; 44 | type.itemType = mTypes.size(); 45 | type.clazz = clazz; 46 | mTypes.add(type); 47 | } 48 | 49 | @Override 50 | public void register(int layoutId) { 51 | 52 | } 53 | 54 | private boolean checkIsRegistered(Class clazz) { 55 | for (Type type : mTypes) { 56 | if (type.clazz == clazz){ 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int itemType) { 64 | Type type = mTypes.get(itemType); 65 | return type.getViewHolder(parent); 66 | } 67 | 68 | public int getItemType(Class clazz) { 69 | for (int i = 0; i < mTypes.size(); i++) { 70 | Type type = mTypes.get(i); 71 | if (type.clazz==clazz){ 72 | return i; 73 | } 74 | } 75 | throw new IllegalStateException(clazz+" 没有注册,请检查.."); 76 | } 77 | 78 | @Override 79 | public int getItemType(int layoutId) { 80 | return 0; 81 | } 82 | 83 | 84 | public static class Type { 85 | int itemType; 86 | int layoutId; 87 | Class clazz; 88 | private LayoutInflater inflater; 89 | 90 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent) { 91 | if (inflater == null) { 92 | inflater = LayoutInflater.from(parent.getContext()); 93 | } 94 | View view = inflater.inflate(layoutId, parent, false); 95 | return new BaseViewHolder(view); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/typepool/MultiTypePoolMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.typepool; 18 | 19 | import android.support.v4.util.ArrayMap; 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | 25 | import com.gersion.library.multitype.viewholder.BaseViewHolder; 26 | 27 | import java.util.Iterator; 28 | import java.util.Map; 29 | import java.util.Set; 30 | 31 | /** 32 | * An List implementation of TypePool. 33 | * 34 | * @author drakeet 35 | */ 36 | public class MultiTypePoolMap implements TypePool { 37 | 38 | private ArrayMap mTypeArrayMap = new ArrayMap<>(); 39 | private static int count = 0; 40 | 41 | public void register(Class clazz, int layoutId) { 42 | Type type = new Type(); 43 | type.layoutId = layoutId; 44 | type.itemType = count++; 45 | mTypeArrayMap.put(clazz, type); 46 | } 47 | 48 | @Override 49 | public void register(int layoutId) { 50 | 51 | } 52 | 53 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int itemType) { 54 | 55 | Set> entries = mTypeArrayMap.entrySet(); 56 | Iterator> iterator = entries.iterator(); 57 | while (iterator.hasNext()) { 58 | Map.Entry entry = iterator.next(); 59 | Type type = entry.getValue(); 60 | if (type.itemType == itemType) { 61 | return type.getViewHolder(parent); 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | public int getItemType(Class clazz) { 68 | Set> entries = mTypeArrayMap.entrySet(); 69 | Iterator> iterator = entries.iterator(); 70 | while (iterator.hasNext()) { 71 | Map.Entry entry = iterator.next(); 72 | Class key = entry.getKey(); 73 | if (key == clazz) { 74 | return entry.getValue().itemType; 75 | } 76 | 77 | } 78 | throw new IllegalStateException(clazz+" 没有注册,请检查.."); 79 | } 80 | 81 | @Override 82 | public int getItemType(int layoutId) { 83 | return 0; 84 | } 85 | 86 | 87 | public static class Type { 88 | int itemType; 89 | int layoutId; 90 | private LayoutInflater inflater; 91 | 92 | public RecyclerView.ViewHolder getViewHolder(ViewGroup parent) { 93 | if (inflater == null) { 94 | inflater = LayoutInflater.from(parent.getContext()); 95 | } 96 | View view = inflater.inflate(layoutId, parent, false); 97 | return new BaseViewHolder<>(view); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/multitype/typepool/TypePool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 drakeet. https://github.com/drakeet 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 | 17 | package com.gersion.library.multitype.typepool; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.ViewGroup; 21 | 22 | /** 23 | * An ordered collection to hold the types, binders and linkers. 24 | * 25 | * @author drakeet 26 | */ 27 | public interface TypePool { 28 | 29 | void register(Class clazz, int layouId); 30 | 31 | void register(int layoutId); 32 | 33 | RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int itemType); 34 | 35 | int getItemType(Class clazz); 36 | 37 | int getItemType(int layoutId); 38 | } 39 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/utils/MatchUtils.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * Created by Gersy on 2016/12/28. 7 | */ 8 | public class MatchUtils { 9 | /** 10 | * ~~ 创建时间:2017/5/25 14:46 ~~ 11 | * 模糊匹配 12 | */ 13 | public static boolean isMatch(String content, String condition) { 14 | if (TextUtils.isEmpty(content) || TextUtils.isEmpty(condition)) { 15 | return false; 16 | } 17 | int minIndex = -1; 18 | char[] chars = condition.toCharArray(); 19 | for (char c : chars) { 20 | int currentIndex = content.indexOf(c,minIndex+1); 21 | if (currentIndex>minIndex){ 22 | minIndex = currentIndex; 23 | }else{ 24 | return false; 25 | } 26 | } 27 | return true; 28 | } 29 | 30 | /** 31 | * ~~ 创建时间:2017/5/25 14:44 ~~ 32 | * 以字符串为单位的匹配 33 | */ 34 | public static int getIndex(String content, String condition) { 35 | if (TextUtils.isEmpty(content) || TextUtils.isEmpty(condition)) { 36 | return -1; 37 | } 38 | return content.indexOf(condition); 39 | } 40 | 41 | /** 42 | * ~~ 创建时间:2017/5/25 14:45 ~~ 43 | * 模糊匹配每个字符,得到每个字符第一次出现的数组 44 | */ 45 | public static int[] getIndexArray(String content, String condition) { 46 | if (TextUtils.isEmpty(content) || TextUtils.isEmpty(condition)) { 47 | return null; 48 | } 49 | int minIndex = -1; 50 | int[] array = new int[condition.length()]; 51 | char[] chars = condition.toCharArray(); 52 | int i = 0; 53 | for (char c : chars) { 54 | int currentIndex = content.indexOf(c,minIndex+1); 55 | if (currentIndex>minIndex){ 56 | minIndex = currentIndex; 57 | array[i++] = currentIndex; 58 | }else{ 59 | return null; 60 | } 61 | } 62 | return array; 63 | } 64 | // public static boolean isMatch(String content, String condition) { 65 | // try { 66 | // if (TextUtils.isEmpty(content) || TextUtils.isEmpty(condition)) { 67 | // return false; 68 | // } 69 | // StringBuilder regex = new StringBuilder(); 70 | // condition = condition.replaceAll(pattern, ""); 71 | // for (int i = 0; i < condition.length(); i++) { 72 | // char c = condition.charAt(i); 73 | // if (c == '.') { 74 | // regex.append(c + "*"); 75 | // } else { 76 | // regex.append(".*" + c); 77 | // } 78 | // if (i == condition.length() - 1) { 79 | // regex.append(".*"); 80 | // } 81 | // } 82 | // if (content.matches(regex.toString())) { 83 | // return true; 84 | // } 85 | // return false; 86 | // } catch (Exception e) { 87 | // LoggerUtils.d(e.getMessage()); 88 | // return false; 89 | // } 90 | // } 91 | } 92 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/utils/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.KeyguardManager; 5 | import android.content.Context; 6 | import android.content.pm.ActivityInfo; 7 | import android.content.res.Configuration; 8 | import android.graphics.Bitmap; 9 | import android.util.DisplayMetrics; 10 | import android.view.Surface; 11 | import android.view.View; 12 | import android.view.WindowManager; 13 | 14 | /** 15 | *
 16 |  *     author: Blankj
 17 |  *     blog  : http://blankj.com
 18 |  *     time  : 2016/8/2
 19 |  *     desc  : 屏幕相关工具类
 20 |  * 
21 | */ 22 | public class ScreenUtils { 23 | 24 | private ScreenUtils() { 25 | throw new UnsupportedOperationException("u can't instantiate me..."); 26 | } 27 | 28 | /** 29 | * 获取屏幕的宽度(单位:px) 30 | * 31 | * @param context 上下文 32 | * @return 屏幕宽px 33 | */ 34 | public static int getScreenWidth(Context context) { 35 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 36 | DisplayMetrics dm = new DisplayMetrics();// 创建了一张白纸 37 | windowManager.getDefaultDisplay().getMetrics(dm);// 给白纸设置宽高 38 | return dm.widthPixels; 39 | } 40 | 41 | /** 42 | * 获取屏幕的高度(单位:px) 43 | * 44 | * @param context 上下文 45 | * @return 屏幕高px 46 | */ 47 | public static int getScreenHeight(Context context) { 48 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 49 | DisplayMetrics dm = new DisplayMetrics();// 创建了一张白纸 50 | windowManager.getDefaultDisplay().getMetrics(dm);// 给白纸设置宽高 51 | return dm.heightPixels; 52 | } 53 | 54 | /** 55 | * 设置屏幕为横屏 56 | *

还有一种就是在Activity中加属性android:screenOrientation="landscape"

57 | *

不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

58 | *

设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

59 | *

设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"(4.0以上必须带最后一个参数)时 60 | * 切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

61 | * 62 | * @param activity activity 63 | */ 64 | public static void setLandscape(Activity activity) { 65 | activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 66 | } 67 | 68 | /** 69 | * 设置屏幕为竖屏 70 | * 71 | * @param activity activity 72 | */ 73 | public static void setPortrait(Activity activity) { 74 | activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 75 | } 76 | 77 | /** 78 | * 判断是否横屏 79 | * 80 | * @param context 上下文 81 | * @return {@code true}: 是
{@code false}: 否 82 | */ 83 | public static boolean isLandscape(Context context) { 84 | return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; 85 | } 86 | 87 | /** 88 | * 判断是否竖屏 89 | * 90 | * @param context 上下文 91 | * @return {@code true}: 是
{@code false}: 否 92 | */ 93 | public static boolean isPortrait(Context context) { 94 | return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 95 | } 96 | 97 | /** 98 | * 获取屏幕旋转角度 99 | * 100 | * @param activity activity 101 | * @return 屏幕旋转角度 102 | */ 103 | public static int getScreenRotation(Activity activity) { 104 | switch (activity.getWindowManager().getDefaultDisplay().getRotation()) { 105 | default: 106 | case Surface.ROTATION_0: 107 | return 0; 108 | case Surface.ROTATION_90: 109 | return 90; 110 | case Surface.ROTATION_180: 111 | return 180; 112 | case Surface.ROTATION_270: 113 | return 270; 114 | } 115 | } 116 | 117 | /** 118 | * 获取当前屏幕截图,包含状态栏 119 | * 120 | * @param activity activity 121 | * @return Bitmap 122 | */ 123 | public static Bitmap captureWithStatusBar(Activity activity) { 124 | View view = activity.getWindow().getDecorView(); 125 | view.setDrawingCacheEnabled(true); 126 | view.buildDrawingCache(); 127 | Bitmap bmp = view.getDrawingCache(); 128 | DisplayMetrics dm = new DisplayMetrics(); 129 | activity.getWindowManager().getDefaultDisplay().getMetrics(dm); 130 | Bitmap ret = Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels); 131 | view.destroyDrawingCache(); 132 | return ret; 133 | } 134 | 135 | /** 136 | * 判断是否锁屏 137 | * 138 | * @param context 上下文 139 | * @return {@code true}: 是
{@code false}: 否 140 | */ 141 | public static boolean isScreenLock(Context context) { 142 | KeyguardManager km = (KeyguardManager) context 143 | .getSystemService(Context.KEYGUARD_SERVICE); 144 | return km.inKeyguardRestrictedInputMode(); 145 | } 146 | } -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/utils/SizeUtils.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.utils; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.util.TypedValue; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | /** 10 | *
 11 |  *     author: Blankj
 12 |  *     blog  : http://blankj.com
 13 |  *     time  : 2016/8/2
 14 |  *     desc  : 尺寸相关工具类
 15 |  * 
16 | */ 17 | public class SizeUtils { 18 | 19 | private SizeUtils() { 20 | throw new UnsupportedOperationException("u can't instantiate me..."); 21 | } 22 | 23 | /** 24 | * dp转px 25 | * 26 | * @param context 上下文 27 | * @param dpValue dp值 28 | * @return px值 29 | */ 30 | public static int dp2px(Context context, float dpValue) { 31 | final float scale = context.getResources().getDisplayMetrics().density; 32 | return (int) (dpValue * scale + 0.5f); 33 | } 34 | 35 | /** 36 | * px转dp 37 | * 38 | * @param context 上下文 39 | * @param pxValue px值 40 | * @return dp值 41 | */ 42 | public static int px2dp(Context context, float pxValue) { 43 | final float scale = context.getResources().getDisplayMetrics().density; 44 | return (int) (pxValue / scale + 0.5f); 45 | } 46 | 47 | /** 48 | * sp转px 49 | * 50 | * @param context 上下文 51 | * @param spValue sp值 52 | * @return px值 53 | */ 54 | public static int sp2px(Context context, float spValue) { 55 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 56 | return (int) (spValue * fontScale + 0.5f); 57 | } 58 | 59 | /** 60 | * px转sp 61 | * 62 | * @param context 上下文 63 | * @param pxValue px值 64 | * @return sp值 65 | */ 66 | public static int px2sp(Context context, float pxValue) { 67 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 68 | return (int) (pxValue / fontScale + 0.5f); 69 | } 70 | 71 | /** 72 | * 各种单位转换 73 | *

该方法存在于TypedValue

74 | * 75 | * @param unit 单位 76 | * @param value 值 77 | * @param metrics DisplayMetrics 78 | * @return 转换结果 79 | */ 80 | public static float applyDimension(int unit, float value, DisplayMetrics metrics) { 81 | switch (unit) { 82 | case TypedValue.COMPLEX_UNIT_PX: 83 | return value; 84 | case TypedValue.COMPLEX_UNIT_DIP: 85 | return value * metrics.density; 86 | case TypedValue.COMPLEX_UNIT_SP: 87 | return value * metrics.scaledDensity; 88 | case TypedValue.COMPLEX_UNIT_PT: 89 | return value * metrics.xdpi * (1.0f / 72); 90 | case TypedValue.COMPLEX_UNIT_IN: 91 | return value * metrics.xdpi; 92 | case TypedValue.COMPLEX_UNIT_MM: 93 | return value * metrics.xdpi * (1.0f / 25.4f); 94 | } 95 | return 0; 96 | } 97 | 98 | /** 99 | * 在onCreate中获取视图的尺寸 100 | *

需回调onGetSizeListener接口,在onGetSize中获取view宽高

101 | *

用法示例如下所示

102 | *
103 |      * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() {
104 |      *     Override
105 |      *     public void onGetSize(View view) {
106 |      *         view.getWidth();
107 |      *     }
108 |      * });
109 |      * 
110 | * 111 | * @param view 视图 112 | * @param listener 监听器 113 | */ 114 | public static void forceGetViewSize(final View view, final onGetSizeListener listener) { 115 | view.post(new Runnable() { 116 | @Override 117 | public void run() { 118 | if (listener != null) { 119 | listener.onGetSize(view); 120 | } 121 | } 122 | }); 123 | } 124 | 125 | /** 126 | * 获取到View尺寸的监听 127 | */ 128 | public interface onGetSizeListener { 129 | void onGetSize(View view); 130 | } 131 | 132 | public static void setListener(onGetSizeListener listener) { 133 | mListener = listener; 134 | } 135 | 136 | private static onGetSizeListener mListener; 137 | 138 | /** 139 | * 测量视图尺寸 140 | * 141 | * @param view 视图 142 | * @return arr[0]: 视图宽度, arr[1]: 视图高度 143 | */ 144 | public static int[] measureView(View view) { 145 | ViewGroup.LayoutParams lp = view.getLayoutParams(); 146 | if (lp == null) { 147 | lp = new ViewGroup.LayoutParams( 148 | ViewGroup.LayoutParams.MATCH_PARENT, 149 | ViewGroup.LayoutParams.WRAP_CONTENT 150 | ); 151 | } 152 | int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width); 153 | int lpHeight = lp.height; 154 | int heightSpec; 155 | if (lpHeight > 0) { 156 | heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY); 157 | } else { 158 | heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 159 | } 160 | view.measure(widthSpec, heightSpec); 161 | return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()}; 162 | } 163 | 164 | /** 165 | * 获取测量视图宽度 166 | * 167 | * @param view 视图 168 | * @return 视图宽度 169 | */ 170 | public static int getMeasuredWidth(View view) { 171 | return measureView(view)[0]; 172 | } 173 | 174 | /** 175 | * 获取测量视图高度 176 | * 177 | * @param view 视图 178 | * @return 视图高度 179 | */ 180 | public static int getMeasuredHeight(View view) { 181 | return measureView(view)[1]; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/MultiSelectView.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.content.Context; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.text.Editable; 13 | import android.text.TextWatcher; 14 | import android.util.AttributeSet; 15 | import android.view.LayoutInflater; 16 | import android.view.View; 17 | import android.view.animation.OvershootInterpolator; 18 | import android.widget.EditText; 19 | import android.widget.FrameLayout; 20 | import android.widget.ImageView; 21 | import android.widget.LinearLayout; 22 | import android.widget.TextView; 23 | 24 | import com.gersion.library.MultiSelecter; 25 | import com.gersion.library.R; 26 | import com.gersion.library.adapter.BaseMultiAdapter; 27 | import com.gersion.library.adapter.SelectIconRvAdapter; 28 | import com.gersion.library.bean.FloatImgBean; 29 | import com.gersion.library.inter.Filter; 30 | import com.gersion.library.inter.OnItemClickListener; 31 | import com.gersion.library.multitype.typepool.TypePool; 32 | import com.gersion.library.utils.ScreenUtils; 33 | import com.gersion.library.utils.SizeUtils; 34 | import com.gersion.library.view.smartrecycleview.SmartRecycleView; 35 | 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | 39 | /** 40 | * Created by gersy on 2017/8/6. 41 | */ 42 | 43 | public class MultiSelectView extends FrameLayout { 44 | 45 | private LinearLayout mContainer; 46 | private RecyclerView mIconRecyclerView; 47 | private EditText mEtSearch; 48 | private SmartRecycleView mSmartRecycleView; 49 | private LinearLayout mLlSelectAll; 50 | private ImageView mIvSelectAll; 51 | private SelectIconRvAdapter mIconListRvAdapter; 52 | private LinearLayoutManager mLinearLayoutManager; 53 | private List mSelectList; 54 | private TextView mTvConfirm; 55 | private int mMaxWidth; 56 | private BaseMultiAdapter mAdapter; 57 | private List mData; 58 | private ImageView mFloatImg_1; 59 | private int mStartX; 60 | private int mStartY; 61 | private FrameLayout mFlContainer; 62 | private View mSourceView; 63 | private TypePool mTypePool; 64 | private ImageView mFloatImg_2; 65 | private ImageView mFloatImg_3; 66 | private List mImagePool = new ArrayList<>(); 67 | private List mMatchList = new ArrayList<>(); 68 | private View mPlaceHolder; 69 | private int mSelectType; 70 | private Context mContext; 71 | private View mView; 72 | private int mItemWidth; 73 | private int mWidth5; 74 | 75 | public MultiSelectView(@NonNull Context context) { 76 | this(context, null); 77 | } 78 | 79 | public MultiSelectView(@NonNull Context context, @Nullable AttributeSet attrs) { 80 | super(context, attrs); 81 | mContext = context; 82 | mView = LayoutInflater.from(context).inflate(R.layout.fragment_selection, this); 83 | } 84 | 85 | public void init() { 86 | initView(); 87 | initData(); 88 | initListener(); 89 | } 90 | 91 | private void initData() { 92 | int screenWidth = ScreenUtils.getScreenWidth(mContext); 93 | mMaxWidth = screenWidth * 2 / 3; 94 | mItemWidth = SizeUtils.dp2px(mContext, 45); 95 | mWidth5 = SizeUtils.dp2px(mContext, 5); 96 | } 97 | 98 | protected T findView(int id) { 99 | return (T) mView.findViewById(id); 100 | } 101 | 102 | private void initView() { 103 | mContainer = findView(R.id.container); 104 | mIconRecyclerView = findView(R.id.icon_recyclerView); 105 | mEtSearch = findView(R.id.et_search); 106 | mSmartRecycleView = findView(R.id.smartRecycleView); 107 | mLlSelectAll = findView(R.id.ll_select_all); 108 | mIvSelectAll = findView(R.id.iv_select_all); 109 | mTvConfirm = findView(R.id.tv_confirm); 110 | mFloatImg_1 = findView(R.id.float_img_1); 111 | mFloatImg_2 = findView(R.id.float_img_2); 112 | mFloatImg_3 = findView(R.id.float_img_3); 113 | mFlContainer = findView(R.id.fl_container); 114 | mPlaceHolder = findView(R.id.placeholder); 115 | 116 | initFloatPool(); 117 | 118 | initRecyclerView(); 119 | } 120 | 121 | private void initFloatPool() { 122 | FloatImgBean bean1 = new FloatImgBean(); 123 | bean1.mImageView = mFloatImg_1; 124 | FloatImgBean bean2 = new FloatImgBean(); 125 | bean2.mImageView = mFloatImg_2; 126 | FloatImgBean bean3 = new FloatImgBean(); 127 | bean3.mImageView = mFloatImg_3; 128 | mImagePool.add(bean1); 129 | mImagePool.add(bean2); 130 | mImagePool.add(bean3); 131 | } 132 | 133 | private FloatImgBean getFloatImg() { 134 | for (FloatImgBean bean : mImagePool) { 135 | if (!bean.mIsAnimator) { 136 | return bean; 137 | } 138 | } 139 | return null; 140 | } 141 | 142 | private void initRecyclerView() { 143 | mIconListRvAdapter = new SelectIconRvAdapter(); 144 | mLinearLayoutManager = new LinearLayoutManager(mContext); 145 | mLinearLayoutManager.setAutoMeasureEnabled(true); 146 | 147 | mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); 148 | mIconRecyclerView.setLayoutManager(mLinearLayoutManager); 149 | mIconRecyclerView.setAdapter(mIconListRvAdapter); 150 | 151 | mSelectList = mIconListRvAdapter.getList(); 152 | 153 | mAdapter.setTypePool(mTypePool); 154 | mSmartRecycleView.setFirstPage(1) 155 | .setAutoRefresh(false) 156 | .setPageSize(20) 157 | .setAdapter(mAdapter) 158 | .loadMoreEnable(false) 159 | .refreshEnable(false) 160 | .setLayoutManger(SmartRecycleView.LayoutManagerType.LINEAR_LAYOUT); 161 | 162 | mSmartRecycleView.handleData(mData); 163 | 164 | } 165 | 166 | public void setTypePool(TypePool typePool) { 167 | this.mTypePool = typePool; 168 | } 169 | 170 | public void setAdapter(BaseMultiAdapter adapter) { 171 | this.mAdapter = adapter; 172 | } 173 | 174 | private void getParentPoint() { 175 | int[] locations = new int[2]; 176 | mIconRecyclerView.getLocationOnScreen(locations); 177 | mStartX = locations[0]; 178 | mStartY = locations[1]; 179 | } 180 | 181 | public void translationView(final View itemView, final Filter item) { 182 | getParentPoint(); 183 | itemView.setClickable(false); 184 | final FloatImgBean floatImg = getFloatImg(); 185 | floatImg.mImageView.setVisibility(View.VISIBLE); 186 | mPlaceHolder.setVisibility(View.VISIBLE); 187 | floatImg.mImageView.setImageResource(item.getImageResource()); 188 | MultiSelecter.mImageLoader.showImage(mContext, item.getImageUrl(), floatImg.mImageView); 189 | floatImg.mIsAnimator = true; 190 | 191 | int[] sourceLocation = new int[2]; 192 | mSourceView.getLocationOnScreen(sourceLocation); 193 | int startX = sourceLocation[0]; 194 | int startY = sourceLocation[1]; 195 | 196 | int[] tagetLocation = new int[2]; 197 | mIconRecyclerView.getLocationOnScreen(tagetLocation); 198 | int endX = tagetLocation[0] + mIconRecyclerView.getWidth() + mWidth5; 199 | 200 | int endY = tagetLocation[1] + mWidth5 * 2; 201 | 202 | animator(itemView, item, floatImg, startX, startY, endX, endY); 203 | } 204 | 205 | private void animator(final View itemView, final Filter item, final FloatImgBean floatImg, int startX, int startY, int endX, int endY) { 206 | ObjectAnimator animatorX = ObjectAnimator.ofFloat(floatImg.mImageView, "translationX", startX - mStartX, endX - mStartX); 207 | ObjectAnimator animatorY = ObjectAnimator.ofFloat(floatImg.mImageView, "translationY", startY - mStartY, endY - mStartY); 208 | AnimatorSet animatorSet = new AnimatorSet(); 209 | animatorSet.playTogether(animatorX, animatorY); 210 | animatorSet.setDuration(calcDuration(startX - endX, startY - endY)); 211 | animatorSet.setInterpolator(new OvershootInterpolator(1.1f)); 212 | animatorSet.start(); 213 | animatorSet.addListener(new AnimatorListenerAdapter() { 214 | @Override 215 | public void onAnimationEnd(Animator animation) { 216 | floatImg.mImageView.setVisibility(View.GONE); 217 | floatImg.mIsAnimator = false; 218 | mIconListRvAdapter.add(item); 219 | mIconRecyclerView.smoothScrollToPosition(mIconListRvAdapter.getItemCount()); 220 | mPlaceHolder.setVisibility(View.GONE); 221 | refreshLayout(false); 222 | itemView.setClickable(true); 223 | } 224 | }); 225 | } 226 | 227 | 228 | private long calcDuration(float targetX, float targetY) { 229 | return (long) (Math.sqrt((targetX * targetX + targetY * targetY)) * 0.7f); 230 | } 231 | 232 | public void handleData(List data) { 233 | mData = data; 234 | if (mSmartRecycleView == null) { 235 | return; 236 | } 237 | mSmartRecycleView.handleData(data); 238 | } 239 | 240 | private void initListener() { 241 | mLlSelectAll.setOnClickListener(new View.OnClickListener() { 242 | @Override 243 | public void onClick(View v) { 244 | if (!mIvSelectAll.isSelected()) { 245 | List selectionList = mAdapter.getSelectionList(); 246 | mIconListRvAdapter.addAllData(selectionList); 247 | } else { 248 | mIconListRvAdapter.clear(); 249 | } 250 | mAdapter.changeAllDataStatus(!mIvSelectAll.isSelected()); 251 | refreshLayout(!mIvSelectAll.isSelected()); 252 | } 253 | }); 254 | 255 | mAdapter.setOnItemClickListener(new OnItemClickListener() { 256 | @Override 257 | public void onItemClick(View v, Filter item, boolean isSelected) { 258 | if (mSelectType == MultiSelecter.MULTI_SELECT) { 259 | if (isSelected) { 260 | mSourceView = v.findViewById(R.id.iv_icon); 261 | translationView(v, item); 262 | 263 | } else { 264 | mIconListRvAdapter.remove(item); 265 | } 266 | } else { 267 | if (isSelected) { 268 | mIconListRvAdapter.addHiddenItem(item); 269 | mIconRecyclerView.smoothScrollToPosition(mIconListRvAdapter.getItemCount()); 270 | } else { 271 | mIconListRvAdapter.remove(item); 272 | } 273 | } 274 | refreshLayout(false); 275 | } 276 | }); 277 | 278 | mIconListRvAdapter.setOnItemClickListener(new OnItemClickListener() { 279 | @Override 280 | public void onItemClick(View v, Filter item, boolean isSelected) { 281 | mAdapter.updateItem(item); 282 | mIconListRvAdapter.remove(item); 283 | refreshLayout(false); 284 | } 285 | }); 286 | 287 | mEtSearch.addTextChangedListener(new TextWatcher() { 288 | @Override 289 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 290 | 291 | } 292 | 293 | @Override 294 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 295 | 296 | } 297 | 298 | @Override 299 | public void afterTextChanged(Editable editable) { 300 | String trim = mEtSearch.getText().toString().trim(); 301 | if (trim.length() == 0) { 302 | mAdapter.setItems(mData); 303 | } else { 304 | mMatchList.clear(); 305 | for (Filter item : mData) { 306 | if (item.isMatch(trim)) { 307 | mMatchList.add(item); 308 | } 309 | } 310 | mAdapter.setItems(mMatchList); 311 | } 312 | } 313 | }); 314 | } 315 | 316 | private void refreshLayout(boolean isSelected) { 317 | mIvSelectAll.setSelected(isSelected); 318 | 319 | if (mAdapter.getSelectionList().size() == mIconListRvAdapter.getItemCount()) { 320 | mIvSelectAll.setSelected(true); 321 | } 322 | 323 | int size = mSelectList.size(); 324 | mTvConfirm.setText(size == 0 ? "确定" : "确定(" + size + ")"); 325 | if (size == 0) { 326 | mTvConfirm.setEnabled(false); 327 | } else { 328 | mTvConfirm.setEnabled(true); 329 | } 330 | 331 | int width = mItemWidth * size; 332 | if (width > mMaxWidth) { 333 | width = mMaxWidth; 334 | } 335 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.WRAP_CONTENT); 336 | mIconRecyclerView.setLayoutParams(params); 337 | mContainer.requestLayout(); 338 | } 339 | 340 | public List getSelectResult(){ 341 | return mIconListRvAdapter.getList(); 342 | } 343 | 344 | public void setSelectType(int selectType) { 345 | mSelectType = selectType; 346 | mAdapter.setSelectType(selectType); 347 | } 348 | 349 | } 350 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/IRVAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Gersy on 2017/5/19. 9 | */ 10 | 11 | public interface IRVAdapter { 12 | RecyclerView.Adapter getAdapter(); 13 | 14 | void setNewData(List data); 15 | 16 | void addData(List data); 17 | 18 | void removeAll(List data); 19 | 20 | void remove(T data); 21 | 22 | List getData(); 23 | 24 | void notifyDataSetChanged(); 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/SmartRecycleView.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.Color; 7 | import android.support.v7.widget.GridLayoutManager; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.support.v7.widget.StaggeredGridLayoutManager; 11 | import android.util.AttributeSet; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.RelativeLayout; 16 | 17 | import com.gersion.library.R; 18 | import com.gersion.library.view.smartrecycleview.ptr2.PullToRefreshLayout; 19 | 20 | import java.util.List; 21 | 22 | 23 | /** 24 | * @作者 Gaogersy 25 | * @版本 26 | * @包名 com.gersion.refreshrecycleview.view 27 | * @待完成 28 | * @创建时间 2017/3/4 29 | */ 30 | public class SmartRecycleView extends RelativeLayout { 31 | 32 | protected PullToRefreshLayout.OnRefreshListener mRefreshListener; 33 | private PullToRefreshLayout mPullRereshLayout; 34 | private Context mContext; 35 | private RecyclerView mRecyclerView; 36 | private View mFailedView; 37 | private View mNoDataView; 38 | private View mLoadingView; 39 | private int mPageSize = 20; 40 | private boolean mIsLoadMore; 41 | private LayoutManagerType mLayoutManagerType; 42 | private IRVAdapter mAdapter; 43 | private int currentPage = 0; 44 | private int firstPage;//第一页的序号 45 | private boolean isFirstLoad = true;//第一次初始化加载 46 | private boolean isRefresh = true;//判断是不是下拉刷新,不然就是上拉加载 47 | private ViewGroup mContainer;//装SmartRecyclerView的容器 48 | 49 | public SmartRecycleView(Context context) { 50 | this(context, null); 51 | } 52 | 53 | public SmartRecycleView(Context context, AttributeSet attrs) { 54 | super(context, attrs); 55 | mContext = context; 56 | initView(); 57 | initListener(); 58 | } 59 | 60 | private void initView() { 61 | mPullRereshLayout = new PullToRefreshLayout(mContext); 62 | addView(mPullRereshLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 63 | ViewGroup.LayoutParams.MATCH_PARENT)); 64 | 65 | mRecyclerView = new RecyclerView(mContext); 66 | mRecyclerView.setHasFixedSize(true); 67 | mRecyclerView.setOverScrollMode(SCROLL_AXIS_NONE); 68 | mPullRereshLayout.addView(mRecyclerView, 69 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 70 | ViewGroup.LayoutParams.WRAP_CONTENT)); 71 | 72 | init(); 73 | } 74 | 75 | private void init() { 76 | if (mFailedView == null) { 77 | mFailedView = LayoutInflater.from(mContext).inflate(R.layout.view_falied, null); 78 | mFailedView.setVisibility(View.GONE); 79 | mFailedView.setOnClickListener(new OnClickListener() { 80 | @Override 81 | public void onClick(View v) { 82 | mFailedView.setVisibility(View.GONE); 83 | mRefreshListener.onRefresh(firstPage); 84 | } 85 | }); 86 | } 87 | addView(mFailedView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 88 | ViewGroup.LayoutParams.MATCH_PARENT)); 89 | 90 | if (mNoDataView == null) { 91 | mNoDataView = LayoutInflater.from(mContext).inflate(R.layout.view_no_data, null); 92 | } 93 | addView(mNoDataView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 94 | ViewGroup.LayoutParams.MATCH_PARENT)); 95 | mNoDataView.setVisibility(View.GONE); 96 | 97 | if (mLoadingView == null) { 98 | mLoadingView = LayoutInflater.from(mContext).inflate(R.layout.view_loading, null); 99 | } 100 | addView(mLoadingView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 101 | ViewGroup.LayoutParams.MATCH_PARENT)); 102 | mLoadingView.setVisibility(View.VISIBLE); 103 | mLoadingView.setBackgroundColor(Color.parseColor("#ffffff")); 104 | 105 | } 106 | 107 | public RecyclerView getRecyclerView() { 108 | return mRecyclerView; 109 | } 110 | 111 | private void initListener() { 112 | } 113 | 114 | /* 115 | * ~~ 时间:2017/5/6 15:38 ~~ 116 | * 下拉刷新成功后数据处理 117 | **/ 118 | public void onRefresh(List data) { 119 | if (data == null) { 120 | if (isFirstLoad) { 121 | setViewStatus(ViewStatus.FAILED); 122 | } else { 123 | mPullRereshLayout.onRefreshErr(); 124 | } 125 | } else { 126 | if (isFirstLoad) { 127 | isFirstLoad = false; 128 | } 129 | if (data.size() == 0) { 130 | setViewStatus(ViewStatus.NO_DATA); 131 | refreshEnable(false); 132 | } else { 133 | setViewStatus(ViewStatus.SUCCESS); 134 | currentPage = firstPage + 1; 135 | mAdapter.setNewData(data); 136 | if (data.size() >= mPageSize) { 137 | mPullRereshLayout.onRefreshSuccess(); 138 | } else { 139 | mPullRereshLayout.onRefreshSuccess(); 140 | loadMoreEnable(false); 141 | } 142 | } 143 | } 144 | mPullRereshLayout.setCurrentPage(currentPage); 145 | } 146 | 147 | /* 148 | * ~~ 时间:2017/5/6 15:38 ~~ 149 | * 加载更多数据成功后的数据处理 150 | **/ 151 | public void onLoadMore(List data) { 152 | if (data == null) { 153 | mPullRereshLayout.onLoadMoreErr(); 154 | } else { 155 | currentPage++; 156 | mAdapter.addData(data); 157 | if (data.size() >= mPageSize) { 158 | mPullRereshLayout.onLoadMoreSuccess(); 159 | } else { 160 | mPullRereshLayout.setNoMoreData(true); 161 | } 162 | } 163 | mPullRereshLayout.setCurrentPage(currentPage); 164 | } 165 | 166 | public void handleData(List data) { 167 | if (isRefresh) { 168 | onRefresh(data); 169 | } else { 170 | onLoadMore(data); 171 | } 172 | } 173 | 174 | public void removeAll(List data) { 175 | mAdapter.getData().removeAll(data); 176 | mAdapter.notifyDataSetChanged(); 177 | if (mAdapter.getData().size() == 0) { 178 | setViewStatus(ViewStatus.NO_DATA); 179 | } 180 | } 181 | 182 | public void onLoadMoreErr() { 183 | mPullRereshLayout.onLoadMoreErr(); 184 | } 185 | 186 | public void onRefreshErr() { 187 | mPullRereshLayout.onLoadMoreErr(); 188 | } 189 | 190 | /* 191 | * ~~ 时间:2017/5/6 17:02 ~~ 192 | * 设置是否自动加载数据 193 | **/ 194 | public SmartRecycleView setAutoRefresh(boolean autoRefresh) { 195 | setViewStatus(ViewStatus.LOADING); 196 | if (autoRefresh) { 197 | mPullRereshLayout.autoRefresh(); 198 | } 199 | return this; 200 | } 201 | 202 | /* 203 | * ~~ 时间:2017/5/6 15:25 ~~ 204 | * 设置第一页的page的值 205 | **/ 206 | public SmartRecycleView setFirstPage(int page) { 207 | this.currentPage = page; 208 | this.firstPage = page; 209 | mPullRereshLayout.setFirstPage(firstPage); 210 | return this; 211 | } 212 | 213 | /* 214 | * ~~ 时间:2017/5/6 15:25 ~~ 215 | * 设置每页面条目个数 216 | **/ 217 | public SmartRecycleView setPageSize(int pageSize) { 218 | this.mPageSize = pageSize; 219 | return this; 220 | } 221 | 222 | public RecyclerView.Adapter getAdapter() { 223 | return mAdapter.getAdapter(); 224 | } 225 | 226 | public SmartRecycleView setAdapter(IRVAdapter adapter) { 227 | mAdapter = adapter; 228 | if (adapter == null) { 229 | throw new NullPointerException("adapter不能为空"); 230 | } 231 | mRecyclerView.setAdapter(adapter.getAdapter()); 232 | return this; 233 | } 234 | 235 | public List getList() { 236 | return mAdapter.getData(); 237 | } 238 | 239 | // public void onRefresh() { 240 | // mPullRereshLayout.setRefreshing(true); 241 | // if (mRefreshListener != null) { 242 | // mRefreshListener.onRefresh(); 243 | // setViewStatus(ViewStatus.LOADING); 244 | // } 245 | // } 246 | 247 | //刷新完成添加列表数据 248 | public void onRefreshComplete(List list) { 249 | mPullRereshLayout.setRefreshing(false); 250 | if (list == null) { 251 | setViewStatus(ViewStatus.FAILED); 252 | } else { 253 | if (list.size() == mPageSize) { 254 | loadMoreEnable(true); 255 | setViewStatus(ViewStatus.SUCCESS); 256 | mAdapter.setNewData(list); 257 | } else { 258 | loadMoreEnable(false); 259 | if (list.size() == 0) { 260 | setViewStatus(ViewStatus.NO_DATA); 261 | } else { 262 | mAdapter.setNewData(list); 263 | setViewStatus(ViewStatus.SUCCESS); 264 | } 265 | } 266 | } 267 | } 268 | 269 | private void setViewStatus(ViewStatus status) { 270 | try { 271 | if (status == ViewStatus.LOADING) { 272 | mLoadingView.setVisibility(VISIBLE); 273 | mNoDataView.setVisibility(GONE); 274 | mFailedView.setVisibility(GONE); 275 | mRecyclerView.setVisibility(GONE); 276 | } else if (status == ViewStatus.FAILED) { 277 | mNoDataView.setVisibility(GONE); 278 | mFailedView.setVisibility(VISIBLE); 279 | mRecyclerView.setVisibility(GONE); 280 | hideView(mLoadingView); 281 | } else if (status == ViewStatus.NO_DATA) { 282 | mNoDataView.setVisibility(VISIBLE); 283 | mFailedView.setVisibility(GONE); 284 | mRecyclerView.setVisibility(GONE); 285 | hideView(mLoadingView); 286 | } else if (status == ViewStatus.SUCCESS) { 287 | mNoDataView.setVisibility(GONE); 288 | mFailedView.setVisibility(GONE); 289 | mRecyclerView.setVisibility(VISIBLE); 290 | hideView(mLoadingView); 291 | } 292 | } catch (NullPointerException e) { 293 | 294 | } 295 | } 296 | 297 | private void hideView(final View view){ 298 | if (view.getVisibility()==GONE){ 299 | return; 300 | } 301 | view.animate() 302 | .alpha(0) 303 | .setDuration(500) 304 | .setListener(new AnimatorListenerAdapter() { 305 | @Override 306 | public void onAnimationEnd(Animator animation) { 307 | view.setVisibility(GONE); 308 | } 309 | }) 310 | .start(); 311 | } 312 | 313 | //数据请求失败调用 314 | public void onLoadFailure() { 315 | mPullRereshLayout.setRefreshing(false); 316 | if (!mIsLoadMore) { 317 | setViewStatus(ViewStatus.FAILED); 318 | } else { 319 | mIsLoadMore = false; 320 | } 321 | } 322 | 323 | public SmartRecycleView setFailedView(View failedView) { 324 | this.mFailedView = failedView; 325 | return this; 326 | } 327 | 328 | public SmartRecycleView setNoDataView(View noDataView) { 329 | this.mNoDataView = noDataView; 330 | return this; 331 | } 332 | 333 | public SmartRecycleView setLoadingView(View loadingView) { 334 | this.mLoadingView = loadingView; 335 | return null; 336 | } 337 | 338 | public SmartRecycleView refreshEnable(boolean enable) { 339 | mPullRereshLayout.setRefreshEnable(enable); 340 | return this; 341 | } 342 | 343 | public SmartRecycleView loadMoreEnable(boolean enable) { 344 | mPullRereshLayout.setLoadMoreEnable(enable); 345 | return this; 346 | } 347 | 348 | public SmartRecycleView setLayoutManger(LayoutManagerType layoutManagerType) { 349 | setLayoutManger(layoutManagerType, LinearLayoutManager.VERTICAL); 350 | return this; 351 | } 352 | 353 | public SmartRecycleView setLayoutManger(LayoutManagerType layoutManagerType, int orientation) { 354 | setLayoutManger(layoutManagerType, orientation, 2); 355 | return this; 356 | } 357 | 358 | public SmartRecycleView setLayoutManger(LayoutManagerType layoutManagerType, int orientation, int spanCout) { 359 | RecyclerView.LayoutManager layoutManager = null; 360 | if (layoutManagerType == LayoutManagerType.LINEAR_LAYOUT) { 361 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext); 362 | linearLayoutManager.setOrientation(orientation); 363 | layoutManager = linearLayoutManager; 364 | } else if (layoutManagerType == LayoutManagerType.GRID_LAYOUT) { 365 | GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, spanCout); 366 | gridLayoutManager.setOrientation(orientation); 367 | layoutManager = gridLayoutManager; 368 | } else if (layoutManagerType == LayoutManagerType.STAGGER_LAYOUT) { 369 | StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(spanCout, orientation); 370 | layoutManager = staggeredGridLayoutManager; 371 | } 372 | mRecyclerView.setLayoutManager(layoutManager); 373 | return this; 374 | } 375 | 376 | public SmartRecycleView setRefreshListener(final PullToRefreshLayout.OnRefreshListener listener) { 377 | mRefreshListener = listener; 378 | PullToRefreshLayout.OnRefreshListener onRefreshListener = new PullToRefreshLayout.OnRefreshListener() { 379 | @Override 380 | public void onRefresh(int page) { 381 | setListener(listener, true, page); 382 | } 383 | 384 | @Override 385 | public void onLoadMore(int page) { 386 | setListener(listener, false, page); 387 | } 388 | }; 389 | mPullRereshLayout.setOnRefreshListener(onRefreshListener); 390 | mPullRereshLayout.setOnRertyListener(onRefreshListener); 391 | return this; 392 | } 393 | 394 | private void setListener(PullToRefreshLayout.OnRefreshListener listener, 395 | boolean isRefresh, int page) { 396 | if (isRefresh) { 397 | this.isRefresh = true; 398 | if (listener != null) { 399 | listener.onRefresh(page); 400 | } 401 | } else { 402 | this.isRefresh = false; 403 | if (listener != null) { 404 | listener.onLoadMore(page); 405 | } 406 | } 407 | } 408 | 409 | protected enum ViewStatus { 410 | LOADING, NO_DATA, FAILED, SUCCESS 411 | } 412 | 413 | public enum LayoutManagerType { 414 | LINEAR_LAYOUT, 415 | GRID_LAYOUT, 416 | STAGGER_LAYOUT, 417 | 418 | //BANNER_LAYOUT, 419 | //FIX_LAYOUT, 420 | //SINGLE_LAYOUT, 421 | //FLOAT_LAYOUT, 422 | //ONEN_LAYOUT, 423 | //COLUMN_LAYOUT, 424 | //STICKY_LAYOUT, 425 | } 426 | 427 | public static class Builder { 428 | private Context mContext; 429 | private View mFailedView; 430 | private View mNoDataView; 431 | private View mLoadingView; 432 | private int mPageSize = 20; 433 | private boolean mIsLoadMore; 434 | private SmartRecycleView.LayoutManagerType mLayoutManagerType; 435 | private IRVAdapter mAdapter; 436 | private int firstPage;//第一页的序号 437 | private boolean isFirstLoad = true;//第一次初始化加载 438 | private boolean isRefresh = true;//判断是不是下拉刷新,不然就是上拉加载 439 | private ViewGroup mContainer; 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/SmartRecycler.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.gersion.library.view.smartrecycleview.ptr2.PullToRefreshLayout; 9 | 10 | 11 | /** 12 | * Created by a3266 on 2017/7/9. 13 | */ 14 | 15 | public class SmartRecycler { 16 | public static final int HORIZONTAL = 0; 17 | public static final int VERTICAL = 1; 18 | private int mOretation; 19 | private int mSpanCount; 20 | private PullToRefreshLayout.OnRefreshListener mRefreshListener; 21 | private Context mContext; 22 | private RecyclerView mRecyclerView; 23 | private View mFailedView; 24 | private View mNoDataView; 25 | private View mLoadingView; 26 | private int mPageSize = 20; 27 | private boolean mIsLoadMore; 28 | private SmartRecycleView.LayoutManagerType mLayoutManagerType; 29 | private IRVAdapter mAdapter; 30 | private int currentPage = 0; 31 | private int firstPage;//第一页的序号 32 | private boolean isFirstLoad = true;//第一次初始化加载 33 | private boolean autoRefresh = false;//第一次初始化加载 34 | private boolean isRefresh = true;//判断是不是下拉刷新,不然就是上拉加载 35 | private SmartRecycleView mSmartRecycleView; 36 | 37 | private SmartRecycler(Builder builder) { 38 | this.mContext = builder.mContext; 39 | this.mFailedView = builder.mFailedView; 40 | this.mNoDataView = builder.mNoDataView; 41 | this.mLoadingView = builder.mLoadingView; 42 | this.mPageSize = builder.mPageSize; 43 | this.mIsLoadMore = builder.mIsLoadMore; 44 | this.mLayoutManagerType = builder.mLayoutManagerType; 45 | this.mAdapter = builder.mAdapter; 46 | this.firstPage = builder.firstPage; 47 | this.isFirstLoad = builder.isFirstLoad; 48 | this.isRefresh = builder.isRefresh; 49 | this.autoRefresh = builder.autoRefresh; 50 | this.mRefreshListener = builder.mRefreshListener; 51 | this.mOretation = builder.mOretation; 52 | this.mSpanCount = builder.mSpanCount; 53 | 54 | if (mContext == null) { 55 | throw new NullPointerException("context 不能为空"); 56 | } 57 | 58 | mSmartRecycleView = new SmartRecycleView(mContext); 59 | mSmartRecycleView.setAdapter(mAdapter) 60 | .setAutoRefresh(autoRefresh) 61 | .loadMoreEnable(mIsLoadMore) 62 | .refreshEnable(isRefresh) 63 | .setFailedView(mFailedView) 64 | .setLoadingView(mLoadingView) 65 | .setNoDataView(mNoDataView) 66 | .setLayoutManger(mLayoutManagerType,mOretation,mSpanCount) 67 | .setFirstPage(firstPage) 68 | .setPageSize(mPageSize) 69 | .setRefreshListener(mRefreshListener); 70 | } 71 | 72 | private SmartRecycleView into(ViewGroup container){ 73 | ViewGroup.LayoutParams params = 74 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 75 | ViewGroup.LayoutParams.MATCH_PARENT); 76 | container.addView(mSmartRecycleView,params); 77 | return mSmartRecycleView; 78 | } 79 | 80 | public static class Builder { 81 | private Context mContext; 82 | private View mFailedView; 83 | private View mNoDataView; 84 | private View mLoadingView; 85 | private int mPageSize = 20; 86 | private int mOretation = VERTICAL; 87 | private int mSpanCount = 2; 88 | private boolean mIsLoadMore; 89 | private SmartRecycleView.LayoutManagerType mLayoutManagerType = SmartRecycleView.LayoutManagerType.LINEAR_LAYOUT; 90 | private IRVAdapter mAdapter; 91 | private int firstPage;//第一页的序号 92 | private boolean isFirstLoad = true;//第一次初始化加载 93 | private boolean autoRefresh; 94 | private boolean isRefresh = true;//判断是不是下拉刷新,不然就是上拉加载 95 | private ViewGroup mContainer; 96 | private PullToRefreshLayout.OnRefreshListener mRefreshListener; 97 | 98 | public Builder(Context context, ViewGroup container, IRVAdapter adapter) { 99 | if (context == null) { 100 | throw new NullPointerException("context 不能为空"); 101 | } 102 | if (adapter == null) { 103 | throw new NullPointerException("adapter不能为空"); 104 | } 105 | if (container==null) { 106 | throw new NullPointerException(" container 不能为空"); 107 | } 108 | this.mContext = context; 109 | this.mAdapter = adapter; 110 | this.mContainer = container; 111 | } 112 | 113 | public Builder setFailedView(View failedView) { 114 | mFailedView = failedView; 115 | return this; 116 | } 117 | 118 | public Builder setNoDataView(View noDataView) { 119 | mNoDataView = noDataView; 120 | return this; 121 | } 122 | 123 | public Builder setLoadingView(View loadingView) { 124 | mLoadingView = loadingView; 125 | return this; 126 | } 127 | 128 | public Builder setPageSize(int pageSize) { 129 | mPageSize = pageSize; 130 | return this; 131 | } 132 | 133 | public Builder setLoadMore(boolean loadMore) { 134 | mIsLoadMore = loadMore; 135 | return this; 136 | } 137 | 138 | public Builder setLayoutManagerType(SmartRecycleView.LayoutManagerType layoutManagerType) { 139 | mLayoutManagerType = layoutManagerType; 140 | return this; 141 | } 142 | 143 | // public Builder setAdapter(IRVAdapter adapter) { 144 | // mAdapter = adapter; 145 | // return this; 146 | // } 147 | 148 | public Builder setFirstPage(int firstPage) { 149 | this.firstPage = firstPage; 150 | return this; 151 | } 152 | 153 | public Builder setFirstLoad(boolean firstLoad) { 154 | isFirstLoad = firstLoad; 155 | return this; 156 | } 157 | 158 | public Builder setRefresh(boolean refresh) { 159 | isRefresh = refresh; 160 | return this; 161 | } 162 | 163 | // public Builder setContainer(ViewGroup container) { 164 | // mContainer = container; 165 | // return this; 166 | // } 167 | 168 | public Builder setAutoRefresh(boolean autoRefresh) { 169 | this.autoRefresh = autoRefresh; 170 | return this; 171 | } 172 | 173 | public Builder setOretation(int oretation) { 174 | mOretation = oretation; 175 | return this; 176 | } 177 | 178 | public Builder setSpanCount(int spanCount) { 179 | mSpanCount = spanCount; 180 | return this; 181 | } 182 | 183 | public Builder setRefreshListener(PullToRefreshLayout.OnRefreshListener refreshListener) { 184 | mRefreshListener = refreshListener; 185 | return this; 186 | } 187 | 188 | public SmartRecycleView build() { 189 | return new SmartRecycler(this).into(mContainer); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/ptr2/CustomLoadLayout.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview.ptr2; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.ImageView; 9 | import android.widget.LinearLayout; 10 | import android.widget.ProgressBar; 11 | import android.widget.TextView; 12 | 13 | import com.gersion.library.R; 14 | 15 | 16 | /** 17 | * Created by a3266 on 2017/5/6. 18 | */ 19 | 20 | public class CustomLoadLayout extends LinearLayout { 21 | 22 | private TextView mTvRefresh; 23 | private OnRetryListener mListener; 24 | private ImageView mIvNarrow; 25 | private ProgressBar mPbLoading; 26 | private boolean isLoadMoreRetry;//上拉加载重试 27 | private boolean isRefreshRetry;//下拉刷新重试 28 | private boolean isCanRetry;//下拉刷新重试 29 | 30 | public CustomLoadLayout(Context context) { 31 | this(context,null); 32 | } 33 | 34 | public CustomLoadLayout(Context context, @Nullable AttributeSet attrs) { 35 | super(context, attrs); 36 | LayoutInflater.from(context).inflate(R.layout.view_load_more, this); 37 | mTvRefresh = (TextView) findViewById(R.id.tv_load_more); 38 | mPbLoading = (ProgressBar) findViewById(R.id.footer_pb_view); 39 | mIvNarrow = (ImageView) findViewById(R.id.footer_image_view); 40 | 41 | init(); 42 | initListener(); 43 | } 44 | 45 | private void init() { 46 | mIvNarrow.setVisibility(View.VISIBLE); 47 | mIvNarrow.setImageResource(R.drawable.down_arrow); 48 | mPbLoading.setVisibility(View.GONE); 49 | } 50 | 51 | private void initListener() { 52 | mTvRefresh.setOnClickListener(new OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | if (isCanRetry) { 56 | isCanRetry = false; 57 | if (mListener != null) { 58 | if (isLoadMoreRetry) { 59 | mListener.onLoadMoreRetry(); 60 | }else if (isRefreshRetry){ 61 | mListener.onRefreshRetry(); 62 | } 63 | } 64 | } 65 | } 66 | }); 67 | } 68 | 69 | public void onPullRefreshEnable(boolean enable){ 70 | mTvRefresh.setText(enable ? "松开刷新" : "下拉刷新"); 71 | mIvNarrow.setVisibility(View.VISIBLE); 72 | mIvNarrow.setRotation(enable ? 180:0); 73 | } 74 | 75 | public void onPullLoadMoreEnable(boolean enable){ 76 | mTvRefresh.setText(enable ? "松开加载" : "上拉加载"); 77 | mIvNarrow.setVisibility(View.VISIBLE); 78 | mIvNarrow.setRotation(enable ? 0 : 180); 79 | } 80 | 81 | public void onPullLoadMoreNoData(){ 82 | mPbLoading.setVisibility(GONE); 83 | mIvNarrow.setVisibility(GONE); 84 | mTvRefresh.setText("没有更多数据了"); 85 | } 86 | 87 | public void onRefreshing(){ 88 | mIvNarrow.setVisibility(View.GONE); 89 | mPbLoading.setVisibility(View.VISIBLE); 90 | mTvRefresh.setText("正在刷新..."); 91 | } 92 | 93 | public void onRefreshError(){ 94 | setRetry(true); 95 | mPbLoading.setVisibility(GONE); 96 | mTvRefresh.setText("刷新失败,点击重试"); 97 | } 98 | 99 | private void setRetry(boolean isRefresh) { 100 | isCanRetry = true; 101 | isLoadMoreRetry = !isRefresh; 102 | isRefreshRetry = isRefresh; 103 | } 104 | 105 | public void onRefreshSuccess(){ 106 | mIvNarrow.setVisibility(View.VISIBLE); 107 | mPbLoading.setVisibility(View.GONE); 108 | mTvRefresh.setText("刷新完成"); 109 | } 110 | 111 | public void onLoading(){ 112 | mIvNarrow.setVisibility(View.GONE); 113 | mPbLoading.setVisibility(View.VISIBLE); 114 | mTvRefresh.setText("正在加载..."); 115 | } 116 | 117 | public void onLoadError(){ 118 | setRetry(false); 119 | mPbLoading.setVisibility(GONE); 120 | mTvRefresh.setText("加载失败,点击重试"); 121 | } 122 | 123 | public void onLoadNoData(){ 124 | mIvNarrow.setVisibility(View.GONE); 125 | mPbLoading.setVisibility(View.GONE); 126 | mTvRefresh.setText("没有更多数据了"); 127 | } 128 | 129 | public void onLoadSuccess(){ 130 | mIvNarrow.setVisibility(View.VISIBLE); 131 | mPbLoading.setVisibility(View.GONE); 132 | mTvRefresh.setText("加载完成"); 133 | } 134 | 135 | public void setOnRetryListener(OnRetryListener listener){ 136 | mListener = listener; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/ptr2/IRVAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview.ptr2; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Gersy on 2017/5/19. 9 | */ 10 | 11 | public interface IRVAdapter { 12 | RecyclerView.Adapter getAdapter(); 13 | 14 | void setNewData(List data); 15 | 16 | void addData(List data); 17 | 18 | void removeAll(List data); 19 | 20 | void remove(T data); 21 | 22 | List getData(); 23 | 24 | void notifyDataSetChanged(); 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/ptr2/OnRetryListener.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview.ptr2; 2 | 3 | public interface OnRetryListener { 4 | void onRefreshRetry(); 5 | void onLoadMoreRetry(); 6 | } 7 | -------------------------------------------------------------------------------- /library/src/main/java/com/gersion/library/view/smartrecycleview/ptr2/PullToRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library.view.smartrecycleview.ptr2; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.util.AttributeSet; 6 | 7 | 8 | /** 9 | * 下拉刷新控件,可以配合 RecyclerView,Scrollview,ListView 10 | * Created by fish on 16/5/17. 11 | */ 12 | public class PullToRefreshLayout extends SuperSwipeRefreshLayout { 13 | 14 | private boolean mLoadMoreEnable; 15 | private boolean mNoMoreData; 16 | private CustomLoadLayout refreshView; 17 | private CustomLoadLayout loadMoreView; 18 | private OnRefreshListener listener; 19 | private int mFirstPage; 20 | private int mCurrentPage; 21 | 22 | public PullToRefreshLayout(Context context) { 23 | super(context); 24 | initLoadingView(true, true); 25 | } 26 | 27 | public PullToRefreshLayout(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | initLoadingView(true, true); 30 | } 31 | 32 | public void setOnRefreshListener(OnRefreshListener listener) { 33 | this.listener = listener; 34 | } 35 | 36 | //一般用于进页面第一次刷新 37 | public void autoRefresh() { 38 | new Handler().postDelayed(new Runnable() { 39 | @Override 40 | public void run() { 41 | // setRefreshing(true); 42 | // refreshView.onRefreshing(); 43 | if (listener != null) { 44 | listener.onRefresh(mFirstPage); 45 | } 46 | } 47 | }, 100); 48 | } 49 | 50 | public void onLoadMoreSuccess() { 51 | loadMoreView.onLoadSuccess(); 52 | setLoadMore(false); 53 | } 54 | 55 | public void onLoadMoreNoData() { 56 | loadMoreView.onLoadNoData(); 57 | postDelayed(new Runnable() { 58 | @Override 59 | public void run() { 60 | setLoadMore(false); 61 | } 62 | },1000); 63 | } 64 | 65 | public void onRefreshSuccess() { 66 | refreshView.onRefreshSuccess(); 67 | setRefreshing(false); 68 | mNoMoreData = false; 69 | } 70 | 71 | public void onLoadMoreErr() { 72 | loadMoreView.onLoadError(); 73 | } 74 | 75 | public void onRefreshErr() { 76 | refreshView.onRefreshError(); 77 | } 78 | 79 | public void initLoadingView(boolean pullDown, boolean pullUp) { 80 | if (pullDown) { 81 | refreshView = new CustomLoadLayout(getContext()); 82 | setHeaderView(refreshView); 83 | setOnPullRefreshListener(new SuperSwipeRefreshLayout.OnPullRefreshListener() { 84 | 85 | @Override 86 | public void onRefresh() { 87 | refreshView.onRefreshing(); 88 | if (listener != null) { 89 | listener.onRefresh(mFirstPage); 90 | } 91 | } 92 | 93 | @Override 94 | public void onPullDistance(int distance) { 95 | // if (distance == 0) { 96 | // refreshView.reset(); 97 | // } 98 | // refreshView.onPull(distance * 1.0f / refreshView.getContentSize()); 99 | } 100 | 101 | @Override 102 | public void onPullEnable(boolean enable) { 103 | refreshView.onPullRefreshEnable(enable); 104 | } 105 | }); 106 | } 107 | 108 | if (pullUp) { 109 | loadMoreView = new CustomLoadLayout(getContext()); 110 | setFooterView(loadMoreView); 111 | setOnPushLoadMoreListener(new OnPushLoadMoreListener() { 112 | @Override 113 | public void onLoadMore() { 114 | if (mNoMoreData) { 115 | onLoadMoreNoData(); 116 | } else { 117 | loadMoreView.onLoading(); 118 | if (listener != null) { 119 | listener.onLoadMore(mCurrentPage); 120 | } 121 | } 122 | } 123 | 124 | @Override 125 | public void onPushDistance(int distance) { 126 | // if (distance == 0) { 127 | // loadMoreView.reset(); 128 | // } 129 | // loadMoreView.onPull(distance * 1.0f / loadMoreView.getContentSize()); 130 | } 131 | 132 | @Override 133 | public void onPushEnable(boolean enable) { 134 | if (mNoMoreData) { 135 | loadMoreView.onPullLoadMoreNoData(); 136 | } else { 137 | loadMoreView.onPullLoadMoreEnable(enable); 138 | } 139 | } 140 | }); 141 | } 142 | 143 | } 144 | 145 | /* 146 | * ~~ 时间:2017/5/6 16:36 ~~ 147 | * 设置网络请求第一页的数字 148 | **/ 149 | public void setFirstPage(int firstPage){ 150 | mFirstPage = firstPage; 151 | } 152 | 153 | /* 154 | * ~~ 时间:2017/5/6 16:39 ~~ 155 | * 设置当前的页数 156 | **/ 157 | public void setCurrentPage(int currentPage){ 158 | mCurrentPage = currentPage; 159 | } 160 | 161 | /* 162 | * ~~ 时间:2017/5/6 15:09 ~~ 163 | * 开启上拉加载 164 | **/ 165 | public void setLoadMoreEnable(boolean enable) { 166 | mLoadMoreEnable = enable; 167 | setPullUpEnable(enable); 168 | } 169 | 170 | /* 171 | * ~~ 时间:2017/5/6 15:10 ~~ 172 | * 开启下拉刷新 173 | **/ 174 | public void setRefreshEnable(boolean enable) { 175 | setPullDownEnable(enable); 176 | } 177 | 178 | /* 179 | * ~~ 时间:2017/5/6 14:49 ~~ 180 | * 设置是否还有更多数据 181 | **/ 182 | public void setNoMoreData(boolean noMoreData) { 183 | mNoMoreData = noMoreData; 184 | setLoadMore(false); 185 | } 186 | 187 | public void setOnRertyListener(final OnRefreshListener listener) { 188 | refreshView.setOnRetryListener(new OnRetryListener() { 189 | @Override 190 | public void onRefreshRetry() { 191 | refreshView.onRefreshing(); 192 | if (listener != null) { 193 | listener.onRefresh(mFirstPage); 194 | } 195 | } 196 | 197 | @Override 198 | public void onLoadMoreRetry() { 199 | 200 | } 201 | }); 202 | loadMoreView.setOnRetryListener(new OnRetryListener() { 203 | @Override 204 | public void onRefreshRetry() { 205 | 206 | } 207 | 208 | @Override 209 | public void onLoadMoreRetry() { 210 | loadMoreView.onLoading(); 211 | if (listener != null) { 212 | listener.onLoadMore(mCurrentPage); 213 | } 214 | } 215 | }); 216 | } 217 | 218 | public interface OnRefreshListener { 219 | void onRefresh(int page); 220 | 221 | void onLoadMore(int page); 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/check_false.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/drawable-xhdpi/check_false.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/check_true.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/drawable-xhdpi/check_true.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/down_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/drawable-xhdpi/down_arrow.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/geycheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/drawable-xhdpi/geycheck.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/add_friend_circle_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/bg_friend_circle_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/btn_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/line_edit_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/public_btn_bg_queding.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fragment_selection.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 24 | 30 | 35 | 50 | 51 | 52 | 57 | 63 | 68 | 73 | 82 | 89 | 96 | 97 | 98 | 113 | 114 | 115 | 116 | 123 | 130 | 137 | -------------------------------------------------------------------------------- /library/src/main/res/layout/item_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /library/src/main/res/layout/item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 23 | 24 | 36 | 37 | 45 | 46 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | -------------------------------------------------------------------------------- /library/src/main/res/layout/view_falied.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | -------------------------------------------------------------------------------- /library/src/main/res/layout/view_load_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /library/src/main/res/layout/view_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /library/src/main/res/layout/view_no_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | -------------------------------------------------------------------------------- /library/src/main/res/layout/view_refresh.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /library/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaoGersy/MultiSelecter/58cfb21ce0112e4c1d5b550b5ea32797f580163a/library/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #fff 4 | #808080 5 | #a0a0a0 6 | #b0b0b0 7 | #c0c0c0 8 | #d0d0d0 9 | #e0e0e0 10 | #f0f0f0 11 | #E9EAEA 12 | #777777 13 | #F2F3F5 14 | #ff9900 15 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | 20dp 5 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Library 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/gersion/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gersion.library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------