├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── ittiger
│ │ └── indexlist
│ │ └── demo
│ │ ├── CityActivity.java
│ │ ├── ContactActivity.java
│ │ ├── IndexStickyViewActivity.java
│ │ ├── IndexStickyViewDecoration.java
│ │ └── entity
│ │ ├── CityEntity.java
│ │ ├── ContactEntity.java
│ │ ├── MenuEntity.java
│ │ └── UserEntity.java
│ └── res
│ ├── drawable-xxhdpi
│ ├── angel.png
│ ├── banner.jpg
│ ├── bell.png
│ ├── christmas.png
│ └── snowman.png
│ ├── drawable
│ ├── ic_toolbar_add.xml
│ ├── vector_add.xml
│ ├── vector_add_focus.xml
│ ├── vector_contact_focus.xml
│ └── vector_del.xml
│ ├── layout
│ ├── activity_city.xml
│ ├── activity_contact.xml
│ ├── activity_index_sticky_view.xml
│ ├── indexsticky_header_contact_banner.xml
│ ├── indexsticky_item_contact.xml
│ └── indexsticky_item_index.xml
│ ├── menu
│ └── toolbar_menu.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gif
└── contact.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── indexstickyview
├── .gitignore
├── bintrayUpload.gradle
├── build.gradle
├── proguard-rules.pro
├── project.properties
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── ittiger
│ │ └── indexlist
│ │ ├── IndexStickyView.java
│ │ ├── IndexValueBus.java
│ │ ├── ItemType.java
│ │ ├── SideBar.java
│ │ ├── adapter
│ │ ├── IndexHeaderFooterAdapter.java
│ │ └── IndexStickyViewAdapter.java
│ │ ├── entity
│ │ ├── BaseEntity.java
│ │ └── IndexStickyEntity.java
│ │ ├── helper
│ │ ├── ComparatorFactory.java
│ │ ├── ConvertHelper.java
│ │ └── PinYinHelper.java
│ │ └── listener
│ │ ├── OnItemClickListener.java
│ │ └── OnItemLongClickListener.java
│ └── res
│ ├── drawable
│ └── indexstickyview_center_overlay_bg.xml
│ └── values
│ └── attrs.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Abstraction issuesJava
39 |
40 |
41 | Android
42 |
43 |
44 | Android Lint
45 |
46 |
47 | Assignment issuesGroovy
48 |
49 |
50 | Assignment issuesJava
51 |
52 |
53 | Bitwise operation issuesJava
54 |
55 |
56 | C/C++
57 |
58 |
59 | Class metricsJava
60 |
61 |
62 | Class structureJava
63 |
64 |
65 | Cloning issuesJava
66 |
67 |
68 | Code maturity issuesJava
69 |
70 |
71 | Code style issuesJava
72 |
73 |
74 | Compiler issuesJava
75 |
76 |
77 | Concurrency annotation issuesJava
78 |
79 |
80 | Control FlowGroovy
81 |
82 |
83 | Control flow issuesJava
84 |
85 |
86 | CorrectnessLintAndroid
87 |
88 |
89 | Data flow analysisC/C++
90 |
91 |
92 | Data flow issuesGroovy
93 |
94 |
95 | Data flow issuesJava
96 |
97 |
98 | Declaration orderC/C++
99 |
100 |
101 | Declaration redundancyGroovy
102 |
103 |
104 | Declaration redundancyJava
105 |
106 |
107 | Dependency issuesJava
108 |
109 |
110 | Encapsulation issuesJava
111 |
112 |
113 | Error handlingGroovy
114 |
115 |
116 | Error handlingJava
117 |
118 |
119 | Finalization issuesJava
120 |
121 |
122 | FunctionsC/C++
123 |
124 |
125 | GPath inspectionsGroovy
126 |
127 |
128 | General
129 |
130 |
131 | GeneralC/C++
132 |
133 |
134 | GeneralJava
135 |
136 |
137 | Google Cloud Endpoints
138 |
139 |
140 | Gradle
141 |
142 |
143 | Groovy
144 |
145 |
146 | HTML
147 |
148 |
149 | ImportsJava
150 |
151 |
152 | Inheritance issuesJava
153 |
154 |
155 | Initialization issuesJava
156 |
157 |
158 | Internationalization issues
159 |
160 |
161 | Internationalization issuesJava
162 |
163 |
164 | J2ME issuesJava
165 |
166 |
167 | JUnit issues
168 |
169 |
170 | JUnit issuesJava
171 |
172 |
173 | Java
174 |
175 |
176 | Java language level issuesJava
177 |
178 |
179 | Java language level migration aidsJava
180 |
181 |
182 | JavaBeans issuesJava
183 |
184 |
185 | Javadoc issuesJava
186 |
187 |
188 | Language Injection
189 |
190 |
191 | LintAndroid
192 |
193 |
194 | Logging issuesJava
195 |
196 |
197 | Manifest
198 |
199 |
200 | Maven
201 |
202 |
203 | Memory issuesJava
204 |
205 |
206 | Method MetricsGroovy
207 |
208 |
209 | Method metricsJava
210 |
211 |
212 | Modularization issuesJava
213 |
214 |
215 | Naming ConventionsGroovy
216 |
217 |
218 | Naming conventionsJava
219 |
220 |
221 | Numeric issuesJava
222 |
223 |
224 | OtherGroovy
225 |
226 |
227 | Packaging issuesJava
228 |
229 |
230 | Performance issuesJava
231 |
232 |
233 | Portability issuesJava
234 |
235 |
236 | Potentially confusing code constructsGroovy
237 |
238 |
239 | Probable bugsGradle
240 |
241 |
242 | Probable bugsGroovy
243 |
244 |
245 | Probable bugsJava
246 |
247 |
248 | Properties Files
249 |
250 |
251 | RELAX NG
252 |
253 |
254 | Resource management issuesJava
255 |
256 |
257 | Security issuesJava
258 |
259 |
260 | Serialization issuesJava
261 |
262 |
263 | TestNG
264 |
265 |
266 | Threading issuesGroovy
267 |
268 |
269 | Threading issuesJava
270 |
271 |
272 | Type checksC/C++
273 |
274 |
275 | Unused codeC/C++
276 |
277 |
278 | Validity issuesGroovy
279 |
280 |
281 | Verbose or redundant code constructsJava
282 |
283 |
284 | Visibility issuesJava
285 |
286 |
287 | XML
288 |
289 |
290 | toString() issuesJava
291 |
292 |
293 |
294 |
295 | Android
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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 | # IndexStickyView
2 | 1. 列表索引
3 | 2. 滑动过程中顶部Title固定
4 | 3. 支持自定义Bar的颜色样式
5 | 4. 支持添加自定义`Header`或`Footer`不同类型的`View`
6 | 5. 支持`ItemClick`和`ItemLongClick`事件
7 | 6. 支持动态添加,删除数据和`Header`及`Footer`
8 |
9 |
10 | ### 个人微信公众号,欢迎扫码关注交流:
11 | 
12 |
13 | # 效果Gif
14 |
15 |
16 | # 使用
17 | ### gradle
18 | 在项目的`build.gradle`文件中添加如下依赖
19 | `compile 'cn.ittiger:indexstickyview:1.1.0'`
20 |
21 | ### xml布局
22 | ```xml
23 | //SideBar的宽度
35 | ```
36 |
37 | ### 集成
38 | 1. 数据实体类实现接口`BaseEntity`
39 | ```java
40 | public class CityEntity implements BaseEntity {
41 | private String mCityName;
42 |
43 | public CityEntity(String cityName) {
44 |
45 | mCityName = cityName;
46 | }
47 |
48 | @Override
49 | public String getIndexField() {
50 |
51 | return mCityName;
52 | }
53 |
54 | public String getCityName() {
55 |
56 | return mCityName;
57 | }
58 | ```
59 | 2. 继承`IndexStickyViewAdapter`
60 | ```java
61 | private class CityAdapter extends IndexStickyViewAdapter {
62 |
63 | public CityAdapter(List originalList) {
64 |
65 | super(originalList);//要展示的数据
66 | }
67 |
68 | @Override
69 | public RecyclerView.ViewHolder onCreateIndexViewHolder(ViewGroup parent) {
70 | ...//索引视图ViewHolder,如:热门城市 视图
71 | }
72 |
73 | @Override
74 | public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
75 | ...//城市内容视图ViewHolder
76 | }
77 |
78 | @Override
79 | public void onBindIndexViewHolder(RecyclerView.ViewHolder holder, int position, String indexName) {
80 | ...//给索引视图绑定数据
81 | }
82 |
83 | @Override
84 | public void onBindContentViewHolder(RecyclerView.ViewHolder holder, int position, CityEntity itemData) {
85 | ...//给内容视图绑定数据
86 | }
87 | }
88 | ```
89 | 3. 设置视图的数据适配器
90 | ```java
91 | CityAdapter adapter = new CityAdapter(initCitys());
92 | mIndexStickyView.setAdapter(mAdapter);//设置数据适配器
93 | adapter.setOnItemClickListener(this);//设置内容项点击响应
94 | adapter.setOnItemLongClickListener(this);//设置内容项长响应
95 | mIndexStickyView.addItemDecoration(...);//设置列表的ItemDecoration
96 | ```
97 | 4. 添加自定义`HeaderView`和`FooterView`
98 | 5. 继承`IndexHeaderFooterAdapter`,有两个构造函数,默认构造函数和三参数构造函数
99 | 6. 使用默认构造函不需要任何实体类,其视图需要100%自定义
100 | 7. 使用三参数构造函数`IndexHeaderFooterAdapter(String indexValue, String indexName, List list)`,三个参数分别为`SideBar`上显示的索引文字,索引视图上显示的索引标题,要显示的实体数据列表。实体列表中的泛型可以和主Adapter中的泛型不一样,但是必须是`BaseEntity`实现。
101 | ```java
102 | //三参数构造函数,索引视图与CityAdapter中的索引视图一样
103 | IndexHeaderFooterAdapter hotCityHeaderAdapter = new IndexHeaderFooterAdapter(
104 | "热", "热门城市", initHotCitys()
105 | ) {
106 | @Override
107 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
108 | ...//HeaderView内容视图ViewHolder
109 | }
110 |
111 | @Override
112 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, CityEntity itemData) {
113 | ...//绑定FooterView内容数据
114 | }
115 | };
116 | ```
117 | 6. `mIndexStickyView.addIndexHeaderAdapter(hotCityHeaderAdapter);`//添加`HeaderView`
118 | 7. `mIndexStickyView.addIndexFooterAdapter(hotCityHeaderAdapter);`//添加`FooterView`
119 | 8. 默认构造函数实现添加`HeaderView`和`FooterView`
120 | ```java
121 | //默认造函数,无数据绑定,视图完全自定义
122 | IndexHeaderFooterAdapter hotCityHeaderAdapter = new IndexHeaderFooterAdapter() {
123 | @Override
124 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
125 | ...//完全自定义的View
126 | }
127 |
128 | @Override
129 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, BaseEntity itemData) {
130 | ...//完全自定义的View
131 | }
132 | };
133 | ```
134 |
135 |
136 |
137 | 具体实现细节请参考`Demo`实现
138 |
139 |
140 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | //butterknife
4 | apply plugin: 'android-apt'
5 |
6 | android {
7 | compileSdkVersion 24
8 | buildToolsVersion "24.0.2"
9 | defaultConfig {
10 | applicationId "cn.ittiger.indexlist.demo"
11 | minSdkVersion 14
12 | targetSdkVersion 24
13 | versionCode 1
14 | versionName "1.0"
15 | vectorDrawables.useSupportLibrary=true
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile project(':indexstickyview')
27 | compile 'com.android.support:appcompat-v7:24.2.1'
28 |
29 | //butterknife
30 | compile 'com.jakewharton:butterknife:8.4.0'
31 | apt 'com.jakewharton:butterknife-compiler:8.4.0'
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 /home/baina/ylhu/program/android-sdk-linux/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/CityActivity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo;
2 |
3 | import butterknife.BindView;
4 | import butterknife.ButterKnife;
5 | import cn.ittiger.indexlist.IndexStickyView;
6 | import cn.ittiger.indexlist.adapter.IndexHeaderFooterAdapter;
7 | import cn.ittiger.indexlist.adapter.IndexStickyViewAdapter;
8 | import cn.ittiger.indexlist.demo.entity.CityEntity;
9 | import cn.ittiger.indexlist.demo.entity.ContactEntity;
10 | import cn.ittiger.indexlist.listener.OnItemClickListener;
11 | import cn.ittiger.indexlist.listener.OnItemLongClickListener;
12 |
13 | import android.content.Context;
14 | import android.graphics.Color;
15 | import android.os.Bundle;
16 | import android.support.v7.app.AppCompatActivity;
17 | import android.support.v7.widget.RecyclerView;
18 | import android.support.v7.widget.Toolbar;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.TextView;
23 | import android.widget.Toast;
24 |
25 | import java.util.ArrayList;
26 | import java.util.Arrays;
27 | import java.util.List;
28 |
29 | /**
30 | * @author: laohu on 2016/12/25
31 | * @site: http://ittiger.cn
32 | */
33 | public class CityActivity extends AppCompatActivity implements OnItemClickListener,
34 | OnItemLongClickListener {
35 | @BindView(R.id.toolbar)
36 | Toolbar mToolbar;
37 | @BindView(R.id.indexStickyView)
38 | IndexStickyView mIndexStickyView;
39 | CityAdapter mAdapter;
40 | private Context mContext;
41 |
42 | @Override
43 | protected void onCreate(Bundle savedInstanceState) {
44 |
45 | super.onCreate(savedInstanceState);
46 | mContext = this;
47 | setContentView(R.layout.activity_city);
48 | ButterKnife.bind(this);
49 | setSupportActionBar(mToolbar);
50 | getSupportActionBar().setTitle("城市列表");
51 |
52 | mIndexStickyView.addItemDecoration(new IndexStickyViewDecoration(this));
53 | mAdapter = new CityAdapter(initCitys());
54 | mAdapter.setOnItemClickListener(this);
55 | mAdapter.setOnItemLongClickListener(this);
56 | mIndexStickyView.setAdapter(mAdapter);
57 |
58 | IndexHeaderFooterAdapter hotCityHeaderAdapter = new IndexHeaderFooterAdapter(
59 | "热", "热门城市", initHotCitys()
60 | ) {
61 | @Override
62 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
63 |
64 | View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
65 | return new CityViewHolder(view);
66 | }
67 |
68 | @Override
69 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, CityEntity itemData) {
70 | CityViewHolder cityViewHolder = (CityViewHolder) holder;
71 | cityViewHolder.mTextView.setText(itemData.getCityName());
72 | }
73 | };
74 | hotCityHeaderAdapter.setOnItemClickListener(this);
75 | hotCityHeaderAdapter.setOnItemLongClickListener(this);
76 | mIndexStickyView.addIndexHeaderAdapter(hotCityHeaderAdapter);
77 |
78 | IndexHeaderFooterAdapter locationCityHeaderAdapter = new IndexHeaderFooterAdapter(
79 | "定", "当前城市", initLocationCitys()
80 | ) {
81 | @Override
82 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
83 |
84 | View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
85 | return new CityViewHolder(view);
86 | }
87 |
88 | @Override
89 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, CityEntity itemData) {
90 | CityViewHolder cityViewHolder = (CityViewHolder) holder;
91 | cityViewHolder.mTextView.setText(itemData.getCityName());
92 | }
93 | };
94 | locationCityHeaderAdapter.setOnItemClickListener(this);
95 | locationCityHeaderAdapter.setOnItemLongClickListener(this);
96 | mIndexStickyView.addIndexHeaderAdapter(locationCityHeaderAdapter);
97 |
98 | }
99 |
100 | List initCitys() {
101 | List list = new ArrayList<>();
102 | // 初始化数据
103 | List contactStrings = Arrays.asList(getResources().getStringArray(R.array.city_array));
104 | for (int i = 0; i < contactStrings.size(); i++) {
105 | CityEntity cityEntity = new CityEntity(contactStrings.get(i));
106 | list.add(cityEntity);
107 | }
108 | return list;
109 | }
110 |
111 | List initHotCitys() {
112 | List list = new ArrayList<>();
113 | list.add(new CityEntity("北京"));
114 | list.add(new CityEntity("上海"));
115 | list.add(new CityEntity("深圳"));
116 | list.add(new CityEntity("武汉"));
117 | return list;
118 | }
119 |
120 | List initLocationCitys() {
121 | List list = new ArrayList<>();
122 | list.add(new CityEntity("武汉"));
123 | return list;
124 | }
125 |
126 | private class CityAdapter extends IndexStickyViewAdapter {
127 |
128 | public CityAdapter(List originalList) {
129 |
130 | super(originalList);
131 | }
132 |
133 | @Override
134 | public RecyclerView.ViewHolder onCreateIndexViewHolder(ViewGroup parent) {
135 |
136 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_index, parent, false);
137 | return new IndexViewHolder(view);
138 | }
139 |
140 | @Override
141 | public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
142 |
143 | View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
144 | return new CityViewHolder(view);
145 | }
146 |
147 | @Override
148 | public void onBindIndexViewHolder(RecyclerView.ViewHolder holder, int position, String indexName) {
149 |
150 | IndexViewHolder indexViewHolder = (IndexViewHolder) holder;
151 | indexViewHolder.mTextView.setText(indexName);
152 | }
153 |
154 | @Override
155 | public void onBindContentViewHolder(RecyclerView.ViewHolder holder, int position, CityEntity itemData) {
156 |
157 | CityViewHolder cityViewHolder = (CityViewHolder) holder;
158 | cityViewHolder.mTextView.setText(itemData.getCityName());
159 | }
160 | }
161 |
162 | class CityViewHolder extends RecyclerView.ViewHolder {
163 | TextView mTextView;
164 | public CityViewHolder(View itemView) {
165 |
166 | super(itemView);
167 | mTextView = (TextView) itemView;
168 | }
169 | }
170 |
171 | class IndexViewHolder extends RecyclerView.ViewHolder {
172 | TextView mTextView;
173 | public IndexViewHolder(View itemView) {
174 |
175 | super(itemView);
176 | mTextView = (TextView) itemView.findViewById(R.id.tv_index);
177 | }
178 | }
179 |
180 | @Override
181 | public void onItemClick(View childView, int position, CityEntity item) {
182 |
183 | Toast.makeText(mContext, "点击:" + item.getCityName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
184 | }
185 |
186 | @Override
187 | public void onItemLongClick(View childView, int position, CityEntity item) {
188 |
189 | Toast.makeText(mContext, "长按:" + item.getCityName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/ContactActivity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo;
2 |
3 | import butterknife.BindView;
4 | import butterknife.ButterKnife;
5 | import cn.ittiger.indexlist.IndexStickyView;
6 | import cn.ittiger.indexlist.adapter.IndexHeaderFooterAdapter;
7 | import cn.ittiger.indexlist.adapter.IndexStickyViewAdapter;
8 | import cn.ittiger.indexlist.demo.entity.ContactEntity;
9 | import cn.ittiger.indexlist.demo.entity.MenuEntity;
10 | import cn.ittiger.indexlist.demo.entity.UserEntity;
11 | import cn.ittiger.indexlist.entity.BaseEntity;
12 | import cn.ittiger.indexlist.listener.OnItemClickListener;
13 | import cn.ittiger.indexlist.listener.OnItemLongClickListener;
14 |
15 | import android.content.Context;
16 | import android.os.Bundle;
17 | import android.support.v4.widget.SwipeRefreshLayout;
18 | import android.support.v7.app.AppCompatActivity;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.support.v7.widget.Toolbar;
21 | import android.view.LayoutInflater;
22 | import android.view.Menu;
23 | import android.view.MenuItem;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.ImageView;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.List;
33 |
34 | /**
35 | * @author: laohu on 2016/12/25
36 | * @site: http://ittiger.cn
37 | */
38 | public class ContactActivity extends AppCompatActivity implements
39 | Toolbar.OnMenuItemClickListener, OnItemClickListener, OnItemLongClickListener {
40 | @BindView(R.id.toolbar)
41 | Toolbar mToolbar;
42 | @BindView(R.id.swipeRefreshLayout)
43 | SwipeRefreshLayout mRefreshLayout;
44 | @BindView(R.id.indexStickyView)
45 | IndexStickyView mIndexStickyView;
46 | MyIndexStickyViewAdapter mAdapter;
47 | private Context mContext;
48 |
49 | IndexHeaderFooterAdapter mFavAdapter;
50 | IndexHeaderFooterAdapter mMenuAdapter;
51 | IndexHeaderFooterAdapter mNormalAdapter;
52 | IndexHeaderFooterAdapter mBannerAdapter;
53 | IndexHeaderFooterAdapter mFooterAdapter;
54 | IndexHeaderFooterAdapter mFooterBannerAdapter;
55 |
56 | @Override
57 | protected void onCreate(Bundle savedInstanceState) {
58 |
59 | super.onCreate(savedInstanceState);
60 | mContext = this;
61 | setContentView(R.layout.activity_contact);
62 | ButterKnife.bind(this);
63 | setSupportActionBar(mToolbar);
64 | getSupportActionBar().setTitle("联系人列表");
65 | mToolbar.setOnMenuItemClickListener(this);
66 | initEditData();
67 |
68 | mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
69 | @Override
70 | public void onRefresh() {
71 | mRefreshLayout.setRefreshing(true);
72 | mRefreshLayout.postDelayed(new Runnable() {
73 | @Override
74 | public void run() {
75 | mAdapter.reset(initDatas());
76 | mRefreshLayout.setRefreshing(false);
77 | }
78 | }, 3000);
79 | }
80 | });
81 |
82 | mAdapter = new MyIndexStickyViewAdapter(initDatas());
83 | mIndexStickyView.setAdapter(mAdapter);
84 | mIndexStickyView.addItemDecoration(new IndexStickyViewDecoration(this));
85 |
86 | //自定义添加头部收藏信息
87 | mFavAdapter = new IndexHeaderFooterAdapter("☆", "我的收藏", initFavDatas()) {
88 | @Override
89 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
90 |
91 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_contact, parent, false);
92 | return new ContentViewHolder(view);
93 | }
94 |
95 | @Override
96 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, UserEntity itemData) {
97 |
98 | ContentViewHolder contentViewHolder = (ContentViewHolder) holder;
99 | contentViewHolder.mMobile.setText(itemData.getMobile());
100 | contentViewHolder.mName.setText(itemData.getName());
101 | }
102 | };
103 | mFavAdapter.setOnItemClickListener(new OnItemClickListener() {
104 | @Override
105 | public void onItemClick(View childView, int position, UserEntity item) {
106 | Toast.makeText(mContext, "点击:" + item.getName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
107 | }
108 | });
109 | mIndexStickyView.addIndexHeaderAdapter(mFavAdapter);
110 |
111 |
112 | //添加自定义头部菜单项
113 | mMenuAdapter = new IndexHeaderFooterAdapter("↑", null, initMenuDatas()) {
114 | @Override
115 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
116 |
117 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_contact, parent, false);
118 | return new ContentViewHolder(view);
119 | }
120 |
121 | @Override
122 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, MenuEntity itemData) {
123 |
124 | ContentViewHolder contentViewHolder = (ContentViewHolder) holder;
125 | contentViewHolder.mMobile.setVisibility(View.GONE);
126 | contentViewHolder.mName.setText(itemData.getMenuTitle());
127 | contentViewHolder.mAvatar.setImageResource(itemData.getMenuIconRes());
128 | }
129 | };
130 | mMenuAdapter.setOnItemLongClickListener(new OnItemLongClickListener() {
131 | @Override
132 | public void onItemLongClick(View childView, int position, MenuEntity item) {
133 | Toast.makeText(mContext, "长按:" + item.getMenuTitle() + ",位置:" + position, Toast.LENGTH_SHORT).show();
134 | }
135 | });
136 | mIndexStickyView.addIndexHeaderAdapter(mMenuAdapter);
137 |
138 | //添加一个长度为1的数据来作为普通视图的数据源
139 | UserEntity[] entitys = {new UserEntity("数据绑定普通HeaderView", "13312345654")};
140 | mNormalAdapter = new IndexHeaderFooterAdapter(null, null, Arrays.asList(entitys)) {
141 | @Override
142 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
143 |
144 | View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
145 | return new MyViewHolder(view);
146 | }
147 |
148 | @Override
149 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, UserEntity itemData) {
150 |
151 | ((MyViewHolder)holder).mTextView.setText(itemData.getName());
152 | }
153 |
154 | class MyViewHolder extends RecyclerView.ViewHolder {
155 | TextView mTextView;
156 |
157 | public MyViewHolder(View itemView) {
158 | super(itemView);
159 | mTextView = (TextView) itemView;
160 | }
161 | }
162 | };
163 | mNormalAdapter.setOnItemClickListener(new OnItemClickListener() {
164 | @Override
165 | public void onItemClick(View childView, int position, UserEntity item) {
166 | Toast.makeText(mContext, "普通Header视图点击:" + item.getName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
167 | }
168 | });
169 | mIndexStickyView.addIndexHeaderAdapter(mNormalAdapter);
170 |
171 | //自定义添加一个图片作为头部普通视图
172 | mBannerAdapter = new IndexHeaderFooterAdapter() {
173 | @Override
174 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
175 |
176 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_header_contact_banner, parent, false);
177 | ImageViewVH vh = new ImageViewVH(view);
178 | vh.img.setOnClickListener(new View.OnClickListener() {
179 | @Override
180 | public void onClick(View v) {
181 | Toast.makeText(mContext, "头部普通图片视图点击", Toast.LENGTH_SHORT).show();
182 | }
183 | });
184 | return vh;
185 | }
186 |
187 | @Override
188 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, BaseEntity itemData) {
189 |
190 | }
191 |
192 | class ImageViewVH extends RecyclerView.ViewHolder {
193 | ImageView img;
194 |
195 | public ImageViewVH(View itemView) {
196 | super(itemView);
197 | img = (ImageView) itemView.findViewById(R.id.img);
198 | }
199 | }
200 | };
201 | mIndexStickyView.addIndexHeaderAdapter(mBannerAdapter);
202 |
203 |
204 | //添加一个底部自定义列表
205 | mFooterAdapter = new IndexHeaderFooterAdapter("$", "Footer", initFavDatas()) {
206 | @Override
207 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
208 |
209 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_contact, parent, false);
210 | return new ContentViewHolder(view);
211 | }
212 |
213 | @Override
214 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, UserEntity itemData) {
215 | ContentViewHolder contentViewHolder = (ContentViewHolder) holder;
216 | contentViewHolder.mMobile.setText(itemData.getMobile());
217 | contentViewHolder.mName.setText(itemData.getName());
218 | }
219 | };
220 | mFooterAdapter.setOnItemClickListener(new OnItemClickListener() {
221 | @Override
222 | public void onItemClick(View childView, int position, UserEntity item) {
223 | Toast.makeText(mContext, "点击Footer:" + item.getName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
224 | }
225 | });
226 | mIndexStickyView.addIndexFooterAdapter(mFooterAdapter);
227 |
228 | //Footer Banner
229 | mFooterBannerAdapter = new IndexHeaderFooterAdapter() {
230 | @Override
231 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
232 |
233 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_header_contact_banner, parent, false);
234 | ImageViewVH vh = new ImageViewVH(view);
235 | vh.img.setOnClickListener(new View.OnClickListener() {
236 | @Override
237 | public void onClick(View v) {
238 | Toast.makeText(mContext, "Footer图片视图点击", Toast.LENGTH_SHORT).show();
239 | }
240 | });
241 | return vh;
242 | }
243 |
244 | @Override
245 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, BaseEntity itemData) {
246 |
247 | }
248 |
249 | class ImageViewVH extends RecyclerView.ViewHolder {
250 | ImageView img;
251 |
252 | public ImageViewVH(View itemView) {
253 | super(itemView);
254 | img = (ImageView) itemView.findViewById(R.id.img);
255 | }
256 | }
257 | };
258 | mIndexStickyView.addIndexFooterAdapter(mFooterBannerAdapter);
259 |
260 |
261 | mAdapter.setOnItemClickListener(this);
262 | mAdapter.setOnItemLongClickListener(this);
263 | }
264 |
265 | private List initDatas() {
266 |
267 | List list = new ArrayList<>();
268 | // 初始化数据
269 | List contactStrings = Arrays.asList(getResources().getStringArray(R.array.contact_array));
270 | List mobileStrings = Arrays.asList(getResources().getStringArray(R.array.mobile_array));
271 | for (int i = 0; i < contactStrings.size(); i++) {
272 | ContactEntity contactEntity = new ContactEntity(contactStrings.get(i), mobileStrings.get(i));
273 | list.add(contactEntity);
274 | }
275 | return list;
276 | }
277 |
278 | private List initFavDatas() {
279 | List list = new ArrayList<>();
280 | list.add(new UserEntity("张三", "13298449923"));
281 | list.add(new UserEntity("李四", "13298449923"));
282 | return list;
283 | }
284 |
285 | private List initMenuDatas() {
286 | List list = new ArrayList<>();
287 | list.add(new MenuEntity("圣诞组", R.drawable.christmas));
288 | list.add(new MenuEntity("雪人组", R.drawable.snowman));
289 | list.add(new MenuEntity("天使组", R.drawable.angel));
290 | list.add(new MenuEntity("铃铛组", R.drawable.bell));
291 | return list;
292 | }
293 |
294 | class MyIndexStickyViewAdapter extends IndexStickyViewAdapter {
295 |
296 | public MyIndexStickyViewAdapter(List list) {
297 |
298 | super(list);
299 | }
300 |
301 | @Override
302 | public RecyclerView.ViewHolder onCreateIndexViewHolder(ViewGroup parent) {
303 |
304 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_index, parent, false);
305 | return new IndexViewHolder(view);
306 | }
307 |
308 | @Override
309 | public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
310 |
311 | View view = LayoutInflater.from(mContext).inflate(R.layout.indexsticky_item_contact, parent, false);
312 | return new ContentViewHolder(view);
313 | }
314 |
315 | @Override
316 | public void onBindIndexViewHolder(RecyclerView.ViewHolder holder, int position, String indexName) {
317 |
318 | IndexViewHolder indexViewHolder = (IndexViewHolder) holder;
319 | indexViewHolder.mTextView.setText(indexName);
320 | }
321 |
322 | @Override
323 | public void onBindContentViewHolder(RecyclerView.ViewHolder holder, int position, ContactEntity itemData) {
324 |
325 | ContentViewHolder contentViewHolder = (ContentViewHolder) holder;
326 | contentViewHolder.mMobile.setText(itemData.getMobile());
327 | contentViewHolder.mName.setText(itemData.getName());
328 | }
329 | }
330 |
331 | class IndexViewHolder extends RecyclerView.ViewHolder {
332 | TextView mTextView;
333 |
334 | public IndexViewHolder(View itemView) {
335 |
336 | super(itemView);
337 | mTextView = (TextView) itemView.findViewById(R.id.tv_index);
338 | }
339 | }
340 |
341 | class ContentViewHolder extends RecyclerView.ViewHolder {
342 | TextView mName;
343 | TextView mMobile;
344 | ImageView mAvatar;
345 |
346 | public ContentViewHolder(View itemView) {
347 |
348 | super(itemView);
349 | mName = (TextView) itemView.findViewById(R.id.tv_name);
350 | mMobile = (TextView) itemView.findViewById(R.id.tv_mobile);
351 | mAvatar = (ImageView) itemView.findViewById(R.id.img_avatar);
352 | }
353 | }
354 |
355 | @Override
356 | public void onItemClick(View childView, int position, ContactEntity item) {
357 |
358 | Toast.makeText(mContext, "点击" + item.getName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
359 | }
360 |
361 | @Override
362 | public void onItemLongClick(View childView, int position, ContactEntity item) {
363 |
364 | Toast.makeText(mContext, "长按:" + item.getName() + ",位置:" + position, Toast.LENGTH_SHORT).show();
365 | }
366 |
367 | @Override
368 | public boolean onCreateOptionsMenu(Menu menu) {
369 |
370 | getMenuInflater().inflate(R.menu.toolbar_menu, menu);
371 | return true;
372 | }
373 |
374 | ContactEntity mAdd = new ContactEntity("阿圆add", "15525672987");
375 | List mAddCollections = new ArrayList<>();
376 | void initEditData() {
377 |
378 | mAddCollections.add(new ContactEntity("阿圆add Collections1", "15525672987"));
379 | mAddCollections.add(new ContactEntity("阿圆add Collections2", "15525672987"));
380 | }
381 |
382 |
383 | @Override
384 | public boolean onMenuItemClick(MenuItem item) {
385 |
386 | switch (item.getItemId()) {
387 | case R.id.toolbar_add:
388 | mAdapter.add(mAdd);
389 | Toast.makeText(mContext, "添加成功", Toast.LENGTH_SHORT).show();
390 | break;
391 | case R.id.toolbar_add_collections:
392 | mAdapter.add(mAddCollections);
393 | Toast.makeText(mContext, "添加成功", Toast.LENGTH_SHORT).show();
394 | break;
395 | case R.id.toolbar_del:
396 | mAdapter.remove(mAdd);
397 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
398 | break;
399 | case R.id.toolbar_del_collections:
400 | mAdapter.remove(mAddCollections);
401 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
402 | break;
403 | case R.id.toolbar_del_Banner_header:
404 | mAdapter.removeHeader(mBannerAdapter);
405 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
406 | break;
407 | case R.id.toolbar_del_normal_header:
408 | mAdapter.removeHeader(mNormalAdapter);
409 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
410 | break;
411 | case R.id.toolbar_del_all_header:
412 | mAdapter.removeAllHeader();
413 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
414 | break;
415 | case R.id.toolbar_del_banner_footer:
416 | mAdapter.removeFooter(mFooterBannerAdapter);
417 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
418 | break;
419 | case R.id.toolbar_del_normal_footer:
420 | mAdapter.removeFooter(mFooterAdapter);
421 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
422 | break;
423 | case R.id.toolbar_del_all_footer:
424 | mAdapter.removeAllFooter();
425 | Toast.makeText(mContext, "删除成功", Toast.LENGTH_SHORT).show();
426 | break;
427 | case R.id.toolbar_clear:
428 | mAdapter.clear();
429 | Toast.makeText(mContext, "清除成功", Toast.LENGTH_SHORT).show();
430 | break;
431 | case R.id.toolbar_reset:
432 | mAdapter.reset(initDatas());
433 | Toast.makeText(mContext, "重置成功", Toast.LENGTH_SHORT).show();
434 | break;
435 | default:
436 | break;
437 | }
438 | return true;
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/IndexStickyViewActivity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo;
2 |
3 | import butterknife.BindView;
4 | import butterknife.ButterKnife;
5 | import butterknife.OnClick;
6 |
7 | import android.content.Intent;
8 | import android.os.Bundle;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.support.v7.widget.Toolbar;
11 | import android.view.View;
12 |
13 | public class IndexStickyViewActivity extends AppCompatActivity {
14 |
15 | @BindView(R.id.toolbar)
16 | Toolbar mToolbar;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 |
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_index_sticky_view);
23 | ButterKnife.bind(this);
24 | setSupportActionBar(mToolbar);
25 | }
26 |
27 | @OnClick({R.id.btnContact, R.id.btnCity})
28 | public void onClick(View view) {
29 | if(view.getId() == R.id.btnCity) {
30 | startActivity(new Intent(this, CityActivity.class));
31 | } else {
32 | startActivity(new Intent(this, ContactActivity.class));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/IndexStickyViewDecoration.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo;
2 |
3 | import cn.ittiger.indexlist.ItemType;
4 | import cn.ittiger.indexlist.adapter.IndexStickyViewAdapter;
5 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
6 |
7 | import android.content.Context;
8 | import android.content.res.TypedArray;
9 | import android.graphics.Canvas;
10 | import android.graphics.Rect;
11 | import android.graphics.drawable.ColorDrawable;
12 | import android.graphics.drawable.Drawable;
13 | import android.support.v7.widget.RecyclerView;
14 | import android.view.View;
15 |
16 | /**
17 | * Created by ylhu on 16-12-22.
18 | */
19 | public class IndexStickyViewDecoration extends RecyclerView.ItemDecoration {
20 |
21 | private final Drawable mDivider;
22 | private final int mSize;
23 | private final int mLeftMargin;
24 | private final int mRightMargin;
25 |
26 | public IndexStickyViewDecoration(Context context) {
27 |
28 | mDivider = new ColorDrawable(context.getResources().getColor(R.color.divider_color));
29 | mSize = context.getResources().getDimensionPixelSize(R.dimen.global_divider_size);
30 | mLeftMargin = context.getResources().getDimensionPixelSize(R.dimen.contact_item_left_margin);
31 | mRightMargin = context.getResources().getDimensionPixelSize(R.dimen.contact_item_right_margin);
32 | }
33 |
34 | @Override
35 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
36 |
37 | int position = parent.getChildAdapterPosition(view);
38 | IndexStickyViewAdapter adapter = (IndexStickyViewAdapter)parent.getAdapter();
39 | IndexStickyEntity entity = adapter.getItem(position);
40 | if(entity.getItemType() != ItemType.ITEM_TYPE_INDEX && position < (adapter.getItemCount() - 1)
41 | && adapter.getItem(position + 1).getItemType() != ItemType.ITEM_TYPE_INDEX) {
42 | outRect.set(0, 0, 0, mSize);
43 | }
44 | }
45 |
46 | @Override
47 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
48 |
49 | super.onDraw(c, parent, state);
50 | int top;
51 | int bottom;
52 | int left = parent.getPaddingLeft() + mLeftMargin;
53 | int right = parent.getWidth() - parent.getPaddingRight() - mRightMargin;
54 | final int childCount = parent.getChildCount();
55 | IndexStickyViewAdapter adapter = (IndexStickyViewAdapter)parent.getAdapter();
56 | IndexStickyEntity entity;
57 | for (int i = 0; i < childCount; i++) {
58 | final View child = parent.getChildAt(i);
59 | int position = parent.getChildAdapterPosition(child);
60 | entity = adapter.getItem(position);
61 | if(entity.getItemType() != ItemType.ITEM_TYPE_INDEX &&
62 | position < (adapter.getItemCount() - 1) &&
63 | adapter.getItem(position + 1).getItemType() != ItemType.ITEM_TYPE_INDEX) {
64 | //获得child的布局信息
65 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();
66 | top = child.getBottom() + params.bottomMargin;
67 | bottom = top + mSize;
68 | mDivider.setBounds(left, top, right, bottom);
69 | mDivider.draw(c);
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/entity/CityEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo.entity;
2 |
3 | import cn.ittiger.indexlist.entity.BaseEntity;
4 |
5 | /**
6 | * @author: laohu on 2016/12/25
7 | * @site: http://ittiger.cn
8 | */
9 |
10 | public class CityEntity implements BaseEntity {
11 | private String mCityName;
12 |
13 | public CityEntity(String cityName) {
14 |
15 | mCityName = cityName;
16 | }
17 |
18 | @Override
19 | public String getIndexField() {
20 |
21 | return mCityName;
22 | }
23 |
24 | public String getCityName() {
25 |
26 | return mCityName;
27 | }
28 |
29 | public void setCityName(String cityName) {
30 |
31 | mCityName = cityName;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/entity/ContactEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo.entity;
2 |
3 | import cn.ittiger.indexlist.entity.BaseEntity;
4 |
5 | /**
6 | * @author: laohu on 2016/12/17
7 | * @site: http://ittiger.cn
8 | */
9 |
10 | public class ContactEntity implements BaseEntity {
11 |
12 | private String name;
13 | private String avatar;
14 | private String mobile;
15 |
16 | public ContactEntity(String name, String mobile) {
17 | this.name = name;
18 | this.mobile = mobile;
19 | }
20 |
21 |
22 | @Override
23 | public String getIndexField() {
24 |
25 | return name;
26 | }
27 |
28 | public String getName() {
29 |
30 | return name;
31 | }
32 |
33 | public void setName(String name) {
34 |
35 | this.name = name;
36 | }
37 |
38 | public String getAvatar() {
39 |
40 | return avatar;
41 | }
42 |
43 | public void setAvatar(String avatar) {
44 |
45 | this.avatar = avatar;
46 | }
47 |
48 | public String getMobile() {
49 |
50 | return mobile;
51 | }
52 |
53 | public void setMobile(String mobile) {
54 |
55 | this.mobile = mobile;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/entity/MenuEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo.entity;
2 |
3 | import cn.ittiger.indexlist.entity.BaseEntity;
4 |
5 | /**
6 | * Created by ylhu on 16-12-21.
7 | */
8 | public class MenuEntity implements BaseEntity {
9 | private long menuId;
10 | private String menuTitle;
11 | private int menuIconRes;
12 |
13 | public MenuEntity(String title, int iconRes) {
14 |
15 | this.menuTitle = title;
16 | this.menuIconRes = iconRes;
17 | }
18 |
19 | public long getMenuId() {
20 |
21 | return menuId;
22 | }
23 |
24 | public void setMenuId(long menuId) {
25 |
26 | this.menuId = menuId;
27 | }
28 |
29 | public String getMenuTitle() {
30 |
31 | return menuTitle;
32 | }
33 |
34 | public void setMenuTitle(String menuTitle) {
35 |
36 | this.menuTitle = menuTitle;
37 | }
38 |
39 | public int getMenuIconRes() {
40 |
41 | return menuIconRes;
42 | }
43 |
44 | public void setMenuIconRes(int menuIconRes) {
45 |
46 | this.menuIconRes = menuIconRes;
47 | }
48 |
49 | @Override
50 | public String getIndexField() {
51 |
52 | return menuTitle;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ittiger/indexlist/demo/entity/UserEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.demo.entity;
2 |
3 | import cn.ittiger.indexlist.entity.BaseEntity;
4 |
5 | /**
6 | * Created by ylhu on 16-12-21.
7 | */
8 | public class UserEntity implements BaseEntity {
9 |
10 | private String name;
11 | private String avatar;
12 | private String mobile;
13 |
14 | public UserEntity(String name, String mobile) {
15 | this.name = name;
16 | this.mobile = mobile;
17 | }
18 |
19 |
20 | @Override
21 | public String getIndexField() {
22 |
23 | return name;
24 | }
25 |
26 | public String getName() {
27 |
28 | return name;
29 | }
30 |
31 | public void setName(String name) {
32 |
33 | this.name = name;
34 | }
35 |
36 | public String getAvatar() {
37 |
38 | return avatar;
39 | }
40 |
41 | public void setAvatar(String avatar) {
42 |
43 | this.avatar = avatar;
44 | }
45 |
46 | public String getMobile() {
47 |
48 | return mobile;
49 | }
50 |
51 | public void setMobile(String mobile) {
52 |
53 | this.mobile = mobile;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/angel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/drawable-xxhdpi/angel.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/drawable-xxhdpi/banner.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/drawable-xxhdpi/bell.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/christmas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/drawable-xxhdpi/christmas.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/snowman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/drawable-xxhdpi/snowman.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_toolbar_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_add.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_add_focus.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_contact_focus.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_del.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_city.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
25 |
26 |
27 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_contact.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
25 |
26 |
27 |
31 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_index_sticky_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
25 |
26 |
27 |
31 |
37 |
38 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/indexsticky_header_contact_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/indexsticky_item_contact.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
18 |
19 |
26 |
27 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/indexsticky_item_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #18B5EA
6 | #c9c9c9
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1px
4 | 10dp
5 | 24dp
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | IndexStickyView
3 |
4 |
5 | - $^*张狗蛋
6 | - 000王二
7 | - 张三
8 |
9 | - boy
10 | - 马蜂窝
11 | - 王了了
12 | - 李玖折
13 | - 胡三
14 | - 张政民
15 | - 陈九
16 | - 白玉芬
17 | - 仓春莲
18 | - 仓红
19 | - 陈超云
20 | - 陈高
21 | - 陈国祥
22 | - 陈小荣
23 | - 陈秀芬
24 | - 陈艳华
25 | - 陈兆国
26 | - 成秀山
27 | - 仇腊梅
28 | - 戴金辉
29 | - 邓海燕
30 | - 翟蕾
31 | - 丁德明
32 | - 丁素琴
33 | - 董荣柱
34 | - 范惠
35 | - 冯霞
36 | - 伏严成
37 | - 皋德华
38 | - 皋歌
39 | - 高艳
40 | - 顾其兰
41 | - 管小云
42 | - 何佳丽
43 | - 侯志玲
44 | - 黄慧
45 | - 黄伟
46 | - 惠志刚
47 | - 季赛花
48 | - 季铜然
49 | - 纪海燕
50 | - 蒋雯
51 | - 康海霞
52 | - 乐峰
53 | - 李金凤
54 | - 李军
55 | - 李晓慧
56 | - 刘琳
57 | - 马丽
58 | - 孟亚军
59 | - 明汉琴
60 | - 倪燕
61 | - 潘杰
62 | - 钱萍
63 | - 邱文干
64 | - 沈亚杰
65 | - 盛静
66 | - 施伟
67 | - 施志胜
68 | - 石磊
69 | - 时扬
70 | - 束长元
71 | - 宋欣
72 | - 孙新东
73 | - 孙正兰
74 | - 汤丽丽
75 | - 唐新胜
76 | - 陶椿燕
77 | - 陶晓雷
78 | - 王丹
79 | - 王霞
80 | - 王晓雷
81 | - 王雪梅
82 | - 吴长凤
83 | - 吴凤祥
84 | - 吴红兵
85 | - 吴华强
86 | - 吴萍
87 | - 夏祥
88 | - 肖丽丽
89 | - 谢莉萍
90 | - 谢艳
91 | - 徐海莲
92 | - 徐进
93 | - 徐丽云
94 | - 徐萍
95 | - 刘世佳
96 |
97 |
98 |
99 | - 18019913883
100 | - 10086
101 | - 12306
102 |
103 | - 18715689899
104 | - 13877898980
105 | - 15678989098
106 | - 10000
107 | - 0557-3130999
108 | - 13907767890
109 | - 13978786823
110 | - 10086
111 | - 12306
112 | - 18019913333
113 | - 18715689899
114 | - 13877898980
115 | - 15678989098
116 | - 10000
117 | - 0557-3130999
118 | - 13907767890
119 | - 13978786823
120 | - 10086
121 | - 12306
122 | - 18019913333
123 | - 18715689899
124 | - 13877898980
125 | - 15678989098
126 | - 10000
127 | - 0557-3130999
128 | - 13907767890
129 | - 13978786823
130 | - 10086
131 | - 12306
132 | - 18019913333
133 | - 18715689899
134 | - 13877898980
135 | - 15678989098
136 | - 10000
137 | - 0557-3130999
138 | - 13907767890
139 | - 13978786823
140 | - 10086
141 | - 12306
142 | - 18019913333
143 | - 18715689899
144 | - 13877898980
145 | - 15678989098
146 | - 10000
147 | - 0557-3130999
148 | - 13907767890
149 | - 13978786823
150 | - 10086
151 | - 12306
152 | - 18019913333
153 | - 18715689899
154 | - 13877898980
155 | - 15678989098
156 | - 10000
157 | - 0557-3130999
158 | - 13907767890
159 | - 13978786823
160 | - 10086
161 | - 12306
162 | - 18019913333
163 | - 18715689899
164 | - 13877898980
165 | - 15678989098
166 | - 10000
167 | - 0557-3130999
168 | - 13907767890
169 | - 13978786823
170 | - 10086
171 | - 12306
172 | - 18019913333
173 | - 18715689899
174 | - 13877898980
175 | - 15678989098
176 | - 10000
177 | - 0557-3130999
178 | - 13907767890
179 | - 13978786823
180 | - 10086
181 | - 12306
182 | - 18019913333
183 | - 18715689899
184 | - 13877898980
185 | - 15678989098
186 | - 10000
187 | - 0557-3130999
188 | - 13907767890
189 | - 13978786823
190 |
191 |
192 |
193 | - 北京市
194 | - 天津市
195 | - 上海市
196 | - 重庆市
197 | - 香港特别行政区
198 | - 澳门特别行政区
199 | - 沈阳市
200 | - 大连市
201 | - 鞍山市
202 | - 抚顺市
203 | - 本溪市
204 | - 丹东市
205 | - 锦州市
206 | - 营口市
207 | - 阜新市
208 | - 辽阳市
209 | - 铁岭市
210 | - 朝阳市
211 | - 盘锦市
212 | - 葫芦岛市
213 | - 哈尔滨市
214 | - 齐齐哈尔市
215 | - 鸡西市
216 | - 鹤岗市
217 | - 双鸭山市
218 | - 大庆市
219 | - 伊春市
220 | - 佳木斯市
221 | - 牡丹江市
222 | - 七台河市
223 | - 黑河市
224 | - 长春市
225 | - 吉林市
226 | - 四平市
227 | - 辽源市
228 | - 通化市
229 | - 白山市
230 | - 松原市
231 | - 白城市
232 | - 延吉市
233 | - 呼和浩特市
234 | - 包头市
235 | - 乌海市
236 | - 赤峰市
237 | - 通辽市
238 | - 鄂尔多斯市
239 | - 呼伦贝尔市
240 | - 巴彦淖尔市
241 | - 乌兰察布市
242 | - 乌兰浩特市
243 | - 二连浩特市
244 | - 乌鲁木齐市
245 | - 克拉玛依市
246 | - 吐鲁番市
247 | - 昌吉市
248 | - 博乐市
249 | - 库尔勒市
250 | - 阿克苏市
251 | - 阿图什市
252 | - 喀什市
253 | - 和田市
254 | - 伊宁市
255 | - 塔城市
256 | - 阿勒泰市
257 | - 西宁市
258 | - 拉萨市
259 | - 兰州市
260 | - 嘉峪关市
261 | - 金昌市
262 | - 白银市
263 | - 天水市
264 | - 武威市
265 | - 张掖市
266 | - 平凉市
267 | - 酒泉市
268 | - 庆阳市
269 | - 定西市
270 | - 陇南市
271 | - 临夏市
272 | - 合作市
273 | - 银川市
274 | - 石嘴山市
275 | - 吴忠市
276 | - 固原市
277 | - 中卫市
278 | - 西安市
279 | - 铜川市
280 | - 宝鸡市
281 | - 咸阳市
282 | - 渭南市
283 | - 延安市
284 | - 汉中市
285 | - 榆林市
286 | - 安康市
287 | - 商洛市
288 | - 太原市
289 | - 大同市
290 | - 阳泉市
291 | - 长治市
292 | - 晋城市
293 | - 朔州市
294 | - 晋中市
295 | - 运城市
296 | - 忻州市
297 | - 临汾市
298 | - 吕梁市
299 | - 石家庄市
300 | - 唐山市
301 | - 秦皇岛市
302 | - 邯郸市
303 | - 邢台市
304 | - 保定市
305 | - 承德市
306 | - 沧州市
307 | - 廊坊市
308 | - 衡水市
309 | - 济南市
310 | - 青岛市
311 | - 淄博市
312 | - 枣庄市
313 | - 东营市
314 | - 烟台市
315 | - 潍坊市
316 | - 济宁市
317 | - 泰安市
318 | - 威海市
319 | - 日照市
320 | - 莱芜市
321 | - 临沂市
322 | - 德州市
323 | - 聊城市
324 | - 滨州市
325 | - 菏泽市
326 | - 南京市
327 | - 无锡市
328 | - 徐州市
329 | - 常州市
330 | - 苏州市
331 | - 南通市
332 | - 连云港市
333 | - 淮安市
334 | - 盐城市
335 | - 扬州市
336 | - 镇江市
337 | - 泰州市
338 | - 宿迁市
339 | - 合肥市
340 | - 芜湖市
341 | - 蚌埠市
342 | - 淮南市
343 | - 马鞍山市
344 | - 淮北市
345 | - 铜陵市
346 | - 安庆市
347 | - 黄山市
348 | - 滁州市
349 | - 阜阳市
350 | - 宿州市
351 | - 巢湖市
352 | - 六安市
353 | - 毫州市
354 | - 池州市
355 | - 宣城市
356 | - 郑州市
357 | - 开封市
358 | - 洛阳市
359 | - 平顶山市
360 | - 安阳市
361 | - 鹤壁市
362 | - 新乡市
363 | - 焦作市
364 | - 濮阳市
365 | - 许昌市
366 | - 漯河市
367 | - 三门峡市
368 | - 南阳市
369 | - 商丘市
370 | - 信阳市
371 | - 周口市
372 | - 驻马店市
373 | - 武汉市
374 | - 黄石市
375 | - 十堰市
376 | - 宜昌市
377 | - 襄樊市
378 | - 鄂州市
379 | - 荆门市
380 | - 孝感市
381 | - 荆州市
382 | - 黄冈市
383 | - 咸宁市
384 | - 随州市
385 | - 恩施市
386 | - 成都市
387 | - 自贡市
388 | - 攀枝花市
389 | - 泸州市
390 | - 德阳市
391 | - 绵阳市
392 | - 广元市
393 | - 遂宁市
394 | - 内江市
395 | - 乐山市
396 | - 南充市
397 | - 眉山市
398 | - 宜宾市
399 | - 内广安市
400 | - 达州市
401 | - 雅安市
402 | - 巴中市
403 | - 资阳市
404 | - 昆明市
405 | - 曲靖市
406 | - 玉溪市
407 | - 保山市
408 | - 昭通市
409 | - 丽江市
410 | - 思茅市
411 | - 临沧市
412 | - 贵阳市
413 | - 六盘水市
414 | - 遵义市
415 | - 安顺市
416 | - 铜仁市
417 | - 兴义市
418 | - 毕节市
419 | - 凯里市
420 | - 都匀市
421 | - 长沙市
422 | - 株洲市
423 | - 湘潭市
424 | - 衡阳市
425 | - 邵阳市
426 | - 岳阳市
427 | - 常德市
428 | - 张家界市
429 | - 益阳市
430 | - 郴州市
431 | - 永州市
432 | - 怀化市
433 | - 娄底市
434 | - 吉首市
435 | - 南昌市
436 | - 萍乡市
437 | - 九江市
438 | - 新余市
439 | - 鹰潭市
440 | - 赣州市
441 | - 吉安市
442 | - 宜春市
443 | - 抚州市
444 | - 上饶市
445 | - 杭州市
446 | - 宁波市
447 | - 温州市
448 | - 嘉兴市
449 | - 湖州市
450 | - 绍兴市
451 | - 金华市
452 | - 衢州市
453 | - 舟山市
454 | - 台州市
455 | - 丽水市
456 | - 福州市
457 | - 厦门市
458 | - 莆田市
459 | - 三明市
460 | - 泉州市
461 | - 漳州市
462 | - 南平市
463 | - 龙岩市
464 | - 宁德市
465 | - 广州市
466 | - 韶关市
467 | - 深圳市
468 | - 珠海市
469 | - 汕头市
470 | - 佛山市
471 | - 江门市
472 | - 湛江市
473 | - 茂名市
474 | - 肇庆市
475 | - 惠州市
476 | - 梅州市
477 | - 汕尾市
478 | - 河源市
479 | - 阳江市
480 | - 清远市
481 | - 东莞市
482 | - 中山市
483 | - 潮州市
484 | - 揭阳市
485 | - 云浮市
486 | - 南宁市
487 | - 柳州市
488 | - 桂林市
489 | - 梧州市
490 | - 北海市
491 | - 防城港市
492 | - 钦州市
493 | - 贵港市
494 | - 玉林市
495 | - 百色市
496 | - 贺州市
497 | - 河池市
498 | - 来宾市
499 | - 崇左市
500 | - 海口市
501 | - 三亚市
502 | - 台北市
503 | - 高雄市
504 | - 台中市
505 |
506 |
507 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.0.0'
9 | //butterknife
10 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
11 |
12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gif/contact.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/gif/contact.gif
--------------------------------------------------------------------------------
/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 | #systemProp.http.proxyHost=127.0.0.1
19 | #systemProp.http.proxyPort=1080
20 | #systemProp.https.proxyHost=127.0.0.1
21 | #systemProp.https.proxyPort=1080
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huyongli/IndexStickyView/0998b9cb677a17b5044b5de3b258bbf562936120/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.14.1-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 |
--------------------------------------------------------------------------------
/indexstickyview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /local.properties
3 | /project.properties
--------------------------------------------------------------------------------
/indexstickyview/bintrayUpload.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.github.dcendents.android-maven'
2 | apply plugin: 'com.jfrog.bintray'
3 |
4 | // load properties
5 | Properties properties = new Properties()
6 | File localPropertiesFile = project.file("local.properties");
7 | if(localPropertiesFile.exists()){
8 | properties.load(localPropertiesFile.newDataInputStream())
9 | }
10 | File projectPropertiesFile = project.file("project.properties");
11 | if(projectPropertiesFile.exists()){
12 | properties.load(projectPropertiesFile.newDataInputStream())
13 | }
14 |
15 | // read properties
16 | def projectName = properties.getProperty("project.name")
17 | def projectGroupId = properties.getProperty("project.groupId")
18 | def projectArtifactId = properties.getProperty("project.artifactId")
19 | def projectVersionName = android.defaultConfig.versionName
20 | def projectPackaging = properties.getProperty("project.packaging")
21 | def projectSiteUrl = properties.getProperty("project.siteUrl")
22 | def projectGitUrl = properties.getProperty("project.gitUrl")
23 |
24 | def developerId = properties.getProperty("developer.id")
25 | def developerName = properties.getProperty("developer.name")
26 | def developerEmail = properties.getProperty("developer.email")
27 |
28 | def bintrayUser = properties.getProperty("bintray.user")
29 | def bintrayApikey = properties.getProperty("bintray.apikey")
30 |
31 | def javadocName = properties.getProperty("javadoc.name")
32 |
33 | group = projectGroupId
34 |
35 | // This generates POM.xml with proper parameters
36 | install {
37 | repositories.mavenInstaller {
38 | pom {
39 | project {
40 | name projectName
41 | groupId projectGroupId
42 | artifactId projectArtifactId
43 | version projectVersionName
44 | packaging projectPackaging
45 | url projectSiteUrl
46 | licenses {
47 | license {
48 | name 'The Apache Software License, Version 2.0'
49 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
50 | }
51 | }
52 | developers {
53 | developer {
54 | id developerId
55 | name developerName
56 | email developerEmail
57 | }
58 | }
59 | scm {
60 | connection projectGitUrl
61 | developerConnection projectGitUrl
62 | url projectSiteUrl
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | // This generates sources.jar
70 | task sourcesJar(type: Jar) {
71 | from android.sourceSets.main.java.srcDirs
72 | classifier = 'sources'
73 | }
74 |
75 | task javadoc(type: Javadoc) {
76 | source = android.sourceSets.main.java.srcDirs
77 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
78 | }
79 |
80 | // This generates javadoc.jar
81 | task javadocJar(type: Jar, dependsOn: javadoc) {
82 | classifier = 'javadoc'
83 | from javadoc.destinationDir
84 | }
85 |
86 | artifacts {
87 | archives javadocJar
88 | archives sourcesJar
89 | }
90 |
91 | // javadoc configuration
92 | javadoc {
93 | options{
94 | encoding "UTF-8"
95 | charSet 'UTF-8'
96 | author true
97 | version projectVersionName
98 | links "http://docs.oracle.com/javase/7/docs/api"
99 | title javadocName
100 | }
101 | }
102 |
103 | // bintray configuration
104 | bintray {
105 | user = bintrayUser
106 | key = bintrayApikey
107 | configurations = ['archives']
108 | pkg {
109 | repo = "maven"
110 | name = projectName
111 | websiteUrl = projectSiteUrl
112 | vcsUrl = projectGitUrl
113 | licenses = ["Apache-2.0"]
114 | publish = true
115 | }
116 | }
--------------------------------------------------------------------------------
/indexstickyview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | // This is the library version used when deploying the artifact
4 | android {
5 | compileSdkVersion 24
6 | buildToolsVersion "24.0.2"
7 |
8 | defaultConfig {
9 | minSdkVersion 11
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile 'com.android.support:recyclerview-v7:24.2.1'
24 | compile 'com.github.promeg:tinypinyin:1.0.0'
25 | }
26 | apply from: "bintrayUpload.gradle"
--------------------------------------------------------------------------------
/indexstickyview/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 /home/baina/ylhu/program/android-sdk-linux/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/indexstickyview/project.properties:
--------------------------------------------------------------------------------
1 | #project.name:项目名称
2 | #project.groupId:项目组ID,通常情况下如果你的包名为com.example.test,那么项目组ID就是com.example
3 | #project.artifactId:项目ID,通常情况下如果你的包名为com.example.test,那么项目ID就是test
4 | #project.packaging:包类型,Android库是aar
5 | #project.siteUrl:项目官方网站的地址,没有的话就用Github上的地址,例如:https://github.com/xiaopansky/Sketch
6 | #project.gitUrl:项目的Git地址,例如:https://github.com/xiaopansky/Sketch.git
7 | #javadoc.name:生成的javadoc打开后主页显示的名称,通常跟项目名称一样即可
8 |
9 |
10 | #project
11 | project.name=indexstickyview
12 | project.groupId=cn.ittiger
13 | project.artifactId=indexstickyview
14 | project.packaging=aar
15 | project.siteUrl=https://github.com/huyongli/IndexStickyView
16 | project.gitUrl=https://github.com/huyongli/IndexStickyView.git
17 |
18 | #javadoc
19 | javadoc.name=IndexStickyView
--------------------------------------------------------------------------------
/indexstickyview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/IndexStickyView.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist;
2 |
3 | import cn.ittiger.indexlist.adapter.IndexHeaderFooterAdapter;
4 | import cn.ittiger.indexlist.adapter.IndexStickyViewAdapter;
5 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
6 | import cn.ittiger.indexstickyview.R;
7 |
8 | import android.content.Context;
9 | import android.content.res.TypedArray;
10 | import android.graphics.Color;
11 | import android.support.v7.widget.LinearLayoutManager;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.text.TextUtils;
14 | import android.util.AttributeSet;
15 | import android.util.TypedValue;
16 | import android.view.Gravity;
17 | import android.view.MotionEvent;
18 | import android.view.View;
19 | import android.widget.RelativeLayout;
20 | import android.widget.TextView;
21 |
22 | import java.util.List;
23 | import java.util.Observable;
24 | import java.util.Observer;
25 |
26 | /**
27 | * 顶部固定的索引列表
28 | * @author: laohu on 2016/12/17
29 | * @site: http://ittiger.cn
30 | */
31 | public class IndexStickyView extends RelativeLayout implements SideBar.OnSideBarTouchListener, Observer {
32 | private static final int DEFAULT_SIDEBAR_WIDTH = 24;//sidebar的默认宽度24dp
33 | private static final int DEFAULT_CENTER_OVERLAY_SIZE = 75;//中间提示视图的size
34 | private static final int DEFAULT_CENTER_OVERLAY_TEXT_COLOR = Color.WHITE;//中间提示视图的文字颜色
35 | private static final int DEFAULT_CENTER_OVERLAY_TEXT_SIZE = 18;//中间提示视图的文字大小
36 |
37 | /**
38 | * 右边索引条
39 | */
40 | private SideBar mSideBar;
41 | /**
42 | * 右边索引条的宽度
43 | */
44 | private int mSideBarWidth;
45 | /**
46 | * 展示数据的RecyclerView
47 | */
48 | private RecyclerView mRecyclerView;
49 | /**
50 | * RecyclerView垂直方向布局
51 | */
52 | private LinearLayoutManager mLinearLayoutManager;
53 | /**
54 | * SideBar滑动或是点击时,中间显示索引文字信息
55 | */
56 | private TextView mCenterOverlayView;
57 | /**
58 | * 固定在头部显示索引提示的视图
59 | */
60 | private RecyclerView.ViewHolder mStickyHeaderView;
61 | /**
62 | * 当前头部固定视图显示的索引值
63 | */
64 | private String mStickyIndexValue;
65 | /**
66 | * 当前视图{@link IndexStickyView} 的数据适配器
67 | */
68 | private IndexStickyViewAdapter mAdapter;
69 |
70 | public IndexStickyView(Context context) {
71 |
72 | this(context, null);
73 | }
74 |
75 | public IndexStickyView(Context context, AttributeSet attrs) {
76 |
77 | this(context, attrs, 0);
78 | }
79 |
80 | public IndexStickyView(Context context, AttributeSet attrs, int defStyleAttr) {
81 |
82 | super(context, attrs, defStyleAttr);
83 | initViewAttrs(context, attrs);
84 | }
85 |
86 | private void initViewAttrs(Context context, AttributeSet attrs) {
87 |
88 | initRecyclerView(context);
89 | initSideBar(context, attrs);
90 | initCenterOverlayView(context);
91 | }
92 |
93 | /**
94 | * 初始化右边SideBar
95 | * @param context
96 | * @param attrs
97 | */
98 | private void initSideBar(Context context, AttributeSet attrs) {
99 |
100 | int defaultSideBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
101 | DEFAULT_SIDEBAR_WIDTH, getResources().getDisplayMetrics());
102 |
103 | mSideBar = new SideBar(context);
104 | mSideBar.setOnSideBarTouchListener(this);
105 | mSideBarWidth = defaultSideBarWidth;
106 | if(attrs != null) {
107 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndexStickyView);
108 | mSideBar.initAttrs(typedArray);
109 | mSideBarWidth = typedArray.getDimensionPixelSize(R.styleable.IndexStickyView_sideBarWidth, defaultSideBarWidth);
110 | typedArray.recycle();
111 | }
112 |
113 | LayoutParams layoutParams = new LayoutParams(mSideBarWidth, LayoutParams.WRAP_CONTENT);
114 | layoutParams.addRule(ALIGN_PARENT_END);
115 | layoutParams.addRule(CENTER_VERTICAL);
116 | addView(mSideBar, layoutParams);
117 | }
118 |
119 | /**
120 | * 初始化数据列表视图
121 | * @param context
122 | */
123 | private void initRecyclerView(Context context) {
124 |
125 | mLinearLayoutManager = new LinearLayoutManager(context);
126 | mRecyclerView = new RecyclerView(context);
127 | mRecyclerView.setVerticalScrollBarEnabled(false);
128 | mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
129 | mRecyclerView.setLayoutManager(mLinearLayoutManager);
130 | mRecyclerView.addOnScrollListener(new RecyclerViewScrollListener());
131 |
132 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
133 | addView(mRecyclerView, layoutParams);
134 | }
135 |
136 | /**
137 | * 初始化滑动或点击SideBar时,中间显示索引信息的视图
138 | * @param context
139 | */
140 | private void initCenterOverlayView(Context context) {
141 |
142 | int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
143 | DEFAULT_CENTER_OVERLAY_TEXT_SIZE, getResources().getDisplayMetrics());
144 |
145 | int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
146 | DEFAULT_CENTER_OVERLAY_SIZE, getResources().getDisplayMetrics());
147 |
148 | mCenterOverlayView = new TextView(context);
149 | mCenterOverlayView.setTextSize(textSize);
150 | mCenterOverlayView.setTextColor(DEFAULT_CENTER_OVERLAY_TEXT_COLOR);
151 | mCenterOverlayView.setGravity(Gravity.CENTER);
152 | mCenterOverlayView.setBackgroundResource(R.drawable.indexstickyview_center_overlay_bg);
153 | mCenterOverlayView.setVisibility(GONE);
154 | LayoutParams layoutParams = new LayoutParams(size, size);
155 | layoutParams.addRule(CENTER_IN_PARENT);
156 | addView(mCenterOverlayView, layoutParams);
157 | }
158 |
159 | /**
160 | * 数据列表滚动监听
161 | */
162 | class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
163 |
164 | @Override
165 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
166 |
167 | super.onScrolled(recyclerView, dx, dy);
168 | int firstVisiblePosition = mLinearLayoutManager.findFirstVisibleItemPosition();
169 | if(firstVisiblePosition < 0 || firstVisiblePosition >= mAdapter.getItemCount()) {
170 | return;
171 | }
172 | IndexStickyEntity entity = mAdapter.getItem(firstVisiblePosition);
173 | mSideBar.setSelectPosition(mSideBar.getPosition(entity.getIndexValue()));
174 | if(TextUtils.isEmpty(entity.getIndexName()) && mStickyHeaderView.itemView.getVisibility() == VISIBLE) {
175 | //如果当前第一个可见项的索引值为空,则当前项可能是普通视图,非索引视图,因此此时需要将mStickyHeaderView进行隐藏
176 | mStickyIndexValue = null;
177 | mStickyHeaderView.itemView.setVisibility(INVISIBLE);
178 | } else {//第一个可见项为索引视图,则需要显示头部固定的索引提示视图
179 | showStickyHeaderView(entity.getIndexName(), firstVisiblePosition);
180 | }
181 |
182 | if(firstVisiblePosition + 1 >= mAdapter.getItemCount()) {
183 | return;
184 | }
185 | //获取第二个可见项实体对象
186 | IndexStickyEntity secondVisibleEntity = mAdapter.getItem(firstVisiblePosition + 1);
187 | if(secondVisibleEntity.getItemType() == ItemType.ITEM_TYPE_INDEX) {
188 | //第二个可见项是索引值视图
189 | View secondVisibleItemView = mLinearLayoutManager.findViewByPosition(firstVisiblePosition + 1);
190 | if(secondVisibleItemView.getTop() <= mStickyHeaderView.itemView.getHeight() && mStickyIndexValue != null) {
191 | //当secondVisibleItemView距顶部的距离 <= mStickyHeaderView的高度时,mStickyHeaderView开始往上滑出
192 | mStickyHeaderView.itemView.setTranslationY(secondVisibleItemView.getTop() - mStickyHeaderView.itemView.getHeight());
193 | }
194 | } else {
195 | //第二个可见项不是索引值视图
196 | if(mStickyHeaderView.itemView.getTranslationY() != 0) {//有偏移
197 | mStickyHeaderView.itemView.setTranslationY(0);
198 | }
199 | }
200 | }
201 | }
202 |
203 | /**
204 | * 显示固定在头部的索引提示视图
205 | * @param indexName
206 | * @param position
207 | */
208 | private void showStickyHeaderView(String indexName, int position) {
209 |
210 | if(indexName == null) {
211 | if(mStickyHeaderView.itemView.getVisibility() != GONE) {
212 | mStickyHeaderView.itemView.setVisibility(GONE);
213 | }
214 | mStickyIndexValue = indexName;
215 | return;
216 | }
217 | if(mStickyHeaderView.itemView.getVisibility() != VISIBLE) {
218 | mStickyHeaderView.itemView.setVisibility(VISIBLE);
219 | }
220 | if(indexName.equals(mStickyIndexValue)) {
221 | return;
222 | }
223 | mStickyIndexValue = indexName;
224 | mAdapter.onBindIndexViewHolder(mStickyHeaderView, position, indexName);
225 | }
226 |
227 | @Override
228 | public void onSideBarTouch(View v, MotionEvent event, int touchPosition) {
229 |
230 | switch (event.getAction()) {
231 | case MotionEvent.ACTION_DOWN:
232 | case MotionEvent.ACTION_MOVE:
233 | showCenterOverlayView(mSideBar.getIndexValue(touchPosition));
234 | if(touchPosition != mSideBar.getSelectPosition()) {
235 | if(touchPosition == 0) {
236 | mLinearLayoutManager.scrollToPosition(0);
237 | } else {
238 | int recyclerViewPosition = getScrollPositionBySideBarSelectPosition(touchPosition);
239 | mLinearLayoutManager.scrollToPositionWithOffset(recyclerViewPosition, 0);
240 | }
241 | }
242 | break;
243 | case MotionEvent.ACTION_CANCEL:
244 | case MotionEvent.ACTION_UP:
245 | hideCenterOverlayView();
246 | break;
247 | }
248 | }
249 |
250 | /**
251 | * 根据SideBar中每个索引值获取该索引值在{@link RecyclerView} 中的位置
252 | * @param touchPosition
253 | * @return
254 | */
255 | private int getScrollPositionBySideBarSelectPosition(int touchPosition) {
256 |
257 | String indexValue = mSideBar.getIndexValue(touchPosition);
258 | return mAdapter.getIndexValuePosition(indexValue);
259 | }
260 |
261 | public void setAdapter(IndexStickyViewAdapter adapter) {
262 |
263 | mAdapter = adapter;
264 | initStickyHeaderView();
265 | mSideBar.setData(adapter.getIndexValueList());
266 | mRecyclerView.setAdapter(adapter);
267 | IndexValueBus.getInstance().clear();
268 | IndexValueBus.getInstance().addObserver(this);
269 | }
270 |
271 | /**
272 | * 添加头部自定义索引数据适配器
273 | * @param adapter
274 | */
275 | public void addIndexHeaderAdapter(IndexHeaderFooterAdapter adapter) {
276 |
277 | if(mAdapter == null) {
278 | throw new NullPointerException("IndexStickyViewAdapter is null, please set IndexStickyViewAdapter first");
279 | }
280 | mAdapter.addIndexHeaderAdapter(adapter);
281 | }
282 |
283 | /**
284 | * 添加底部自定义索引数据适配器
285 | * @param adapter
286 | */
287 | public void addIndexFooterAdapter(IndexHeaderFooterAdapter adapter) {
288 |
289 | if(mAdapter == null) {
290 | throw new NullPointerException("IndexStickyViewAdapter is null, please set IndexStickyViewAdapter first");
291 | }
292 | mAdapter.addIndexFooterAdapter(adapter);
293 | }
294 |
295 | /**
296 | * 初始化{@link #mStickyHeaderView}
297 | */
298 | private void initStickyHeaderView() {
299 |
300 | if(mStickyHeaderView == null) {
301 | mStickyHeaderView = mAdapter.onCreateIndexViewHolder(mRecyclerView);
302 | mStickyHeaderView.itemView.setVisibility(INVISIBLE);
303 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
304 | layoutParams.addRule(ALIGN_PARENT_TOP);
305 | for(int i = 0; i < getChildCount(); i++) {
306 | if(getChildAt(i) == mSideBar) {
307 | addView(mStickyHeaderView.itemView, i, layoutParams);
308 | break;
309 | }
310 | }
311 | }
312 | }
313 |
314 | /**
315 | * 显示中间的索引提示
316 | * @param indexValue
317 | */
318 | private void showCenterOverlayView(String indexValue) {
319 |
320 | if(mCenterOverlayView.getVisibility() != VISIBLE) {
321 | mCenterOverlayView.setVisibility(VISIBLE);
322 | }
323 | if(!mCenterOverlayView.getText().equals(indexValue)) {
324 | mCenterOverlayView.setText(indexValue);
325 | }
326 | }
327 |
328 | /**
329 | * 隐藏中的索引值提示
330 | */
331 | private void hideCenterOverlayView() {
332 |
333 | if(mCenterOverlayView.getVisibility() != GONE) {
334 | postDelayed(new Runnable() {
335 | @Override
336 | public void run() {
337 | mCenterOverlayView.setVisibility(GONE);
338 | }
339 | }, 100);
340 | }
341 | }
342 |
343 | public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration) {
344 |
345 | mRecyclerView.addItemDecoration(itemDecoration);
346 | }
347 |
348 | @Override
349 | public void update(Observable observable, Object data) {
350 |
351 | if(data == null) return;
352 | List indexValueList = (List) data;
353 | mSideBar.setData(indexValueList);
354 | mLinearLayoutManager.scrollToPosition(0);
355 | }
356 |
357 | public int getSideBarWidth() {
358 |
359 | return mSideBar.getWidth();
360 | }
361 | }
362 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/IndexValueBus.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist;
2 |
3 | import java.util.List;
4 | import java.util.Observable;
5 | import java.util.Observer;
6 |
7 | /**
8 | * 观察者模式实现
9 | * @author: laohu on 2016/12/20
10 | * @site: http://ittiger.cn
11 | */
12 | public class IndexValueBus {
13 | private static volatile IndexValueBus sEntityBus;
14 | private EntityObservable mEntityObservable;
15 |
16 | private IndexValueBus() {
17 |
18 | mEntityObservable = new EntityObservable();
19 | }
20 |
21 | public static IndexValueBus getInstance() {
22 |
23 | if(sEntityBus == null) {
24 | synchronized (IndexValueBus.class) {
25 | if(sEntityBus == null) {
26 | sEntityBus = new IndexValueBus();
27 | }
28 | }
29 | }
30 | return sEntityBus;
31 | }
32 |
33 | public void addObserver(Observer observer) {
34 |
35 | mEntityObservable.addObserver(observer);
36 | }
37 |
38 | public void clear() {
39 |
40 | mEntityObservable.deleteObservers();
41 | }
42 |
43 | public void notifyDataSetChanged(List indexValue) {
44 |
45 | mEntityObservable.observaleChanged();
46 | mEntityObservable.notifyObservers(indexValue);
47 | }
48 |
49 | public static class EntityObservable extends Observable {
50 |
51 | public void observaleChanged() {
52 |
53 | this.setChanged();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/ItemType.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist;
2 |
3 | /**
4 | * @author: laohu on 2016/12/17
5 | * @site: http://ittiger.cn
6 | */
7 |
8 | public class ItemType {
9 | /**
10 | * 列表中普通数据项类型,例如联系人列表中的:联系人信息项
11 | */
12 | public static final int ITEM_TYPE_CONTENT = 1000000;
13 | /**
14 | * 列表中索引项类型,例如联系人列表中的:A,B,C...等索引数据
15 | */
16 | public static final int ITEM_TYPE_INDEX = 2000000;
17 |
18 | /**
19 | * 列表中增加头部索引数据(如自定义的常用联系人)
20 | */
21 | public static final int ITEM_TYPE_INDEX_HEADER = 3000000;
22 | /**
23 | * 列表中增加底部索引数据
24 | */
25 | public static final int ITEM_TYPE_INDEX_FOOTER = 4000000;
26 | }
27 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/SideBar.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist;
2 |
3 | import cn.ittiger.indexstickyview.R;
4 |
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.text.TextPaint;
11 | import android.util.AttributeSet;
12 | import android.util.TypedValue;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | * 类似联系人的索引bar
21 | * Created by ylhu on 16-12-16.
22 | */
23 | class SideBar extends View implements View.OnTouchListener {
24 | private static final int DEFAULT_TEXT_COLOR = Color.parseColor("#8c8c8c");//默认文字颜色
25 | private static final int DEFAULT_TEXT_SIZE = 14;//默认文字大小14sp
26 | private static final int DEFAULT_FOCUS_TEXT_COLOR = Color.WHITE;//默认选中时的文字颜色
27 | private static final int DEFAULT_FOCUS_BACKGROUND_COLOR = Color.parseColor("#f33737");//默认选中时的背景色
28 | private static final int DEFAULT_BAR_ITEM_SPACE = 4;//每隔bar item间的间隔,4dp
29 | private static final int DEFAULT_BAR_BACKGROUND = Color.TRANSPARENT;//默认bar的背景色
30 |
31 | /**
32 | * 计算出来的高度
33 | */
34 | private int mCalViewHeight;
35 | /**
36 | * 每一个Item的高度
37 | */
38 | private float mItemHeight;
39 | /**
40 | * 要显示的索引文字列表:{#,A,B,C,D....}
41 | */
42 | private List mValueList = new ArrayList<>();
43 | /**
44 | * 选中文字的索引
45 | */
46 | private int mSelectPosition;
47 | /**
48 | * 文字颜色
49 | */
50 | private int mTextColor;
51 | /**
52 | * 选中文字颜色
53 | */
54 | private int mFocusTextColor;
55 | /**
56 | * 选中文的背景色
57 | */
58 | private int mFocusBackgroundColor;
59 | /**
60 | * 文字字体大小
61 | */
62 | private float mTextSize;
63 | /**
64 | * 文字间距
65 | */
66 | private float mTextSpace;
67 |
68 | /**
69 | * 文字画笔
70 | */
71 | private TextPaint mTextPaint;
72 | /**
73 | * 选中文字画笔
74 | */
75 | private TextPaint mFocusTextPaint;
76 | /**
77 | * 当前视图背景色
78 | */
79 | private int mBackground;
80 | /**
81 | * 选中文字的背景圆画笔
82 | */
83 | private Paint mFocusTextBgPaint;
84 |
85 | private OnSideBarTouchListener mOnSideBarTouchListener;
86 |
87 | public SideBar(Context context) {
88 |
89 | this(context, null);
90 | }
91 |
92 | public SideBar(Context context, AttributeSet attrs) {
93 |
94 | this(context, attrs, 0);
95 | }
96 |
97 | public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {
98 |
99 | super(context, attrs, defStyleAttr);
100 | setOnTouchListener(this);
101 | }
102 |
103 | /**
104 | * 初始化相关属性参数
105 | *
106 | * @param typedArray
107 | */
108 | public void initAttrs(TypedArray typedArray) {
109 |
110 | mTextColor = typedArray.getColor(R.styleable.IndexStickyView_sideBarTextColor, DEFAULT_TEXT_COLOR);
111 | mFocusTextColor = typedArray.getColor(R.styleable.IndexStickyView_sideBarFocusTextColor, DEFAULT_FOCUS_TEXT_COLOR);
112 | mFocusBackgroundColor = typedArray.getColor(R.styleable.IndexStickyView_sideBarFocusBackgroundColor, DEFAULT_FOCUS_BACKGROUND_COLOR);
113 | mTextSize = typedArray.getDimensionPixelSize(R.styleable.IndexStickyView_sideBarTextSize, 0);
114 | mTextSpace = typedArray.getDimensionPixelSize(R.styleable.IndexStickyView_sideBarTextSpace, 0);
115 | mBackground = typedArray.getColor(R.styleable.IndexStickyView_sideBarBackgroundColor, DEFAULT_BAR_BACKGROUND);
116 |
117 | initDefaultAttrs();
118 | initTextPaint();
119 | setBackgroundColor(mBackground);
120 | }
121 |
122 | /**
123 | * 初始化相关默认值
124 | */
125 | private void initDefaultAttrs() {
126 |
127 | if(mTextColor == 0) {
128 | mTextColor = DEFAULT_TEXT_COLOR;
129 | }
130 | if(mFocusTextColor == 0) {
131 | mFocusTextColor = DEFAULT_FOCUS_TEXT_COLOR;
132 | }
133 | if(mFocusBackgroundColor == 0) {
134 | mFocusBackgroundColor = DEFAULT_FOCUS_BACKGROUND_COLOR;
135 | }
136 | if(mTextSize == 0) {
137 | mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE, getResources().getDisplayMetrics());
138 | }
139 | if(mTextSpace == 0) {
140 | mTextSpace = getResources().getDisplayMetrics().density * DEFAULT_BAR_ITEM_SPACE;
141 | }
142 | if(mBackground == 0) {
143 | mBackground = DEFAULT_BAR_BACKGROUND;
144 | }
145 | }
146 |
147 | private void initTextPaint() {
148 |
149 | mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
150 | mTextPaint.setTextSize(mTextSize);
151 | mTextPaint.setTextAlign(Paint.Align.CENTER);
152 | mTextPaint.setColor(mTextColor);
153 |
154 | mFocusTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
155 | mFocusTextPaint.setTextSize(mTextSize);
156 | mFocusTextPaint.setTextAlign(Paint.Align.CENTER);
157 | mFocusTextPaint.setColor(mFocusTextColor);
158 |
159 | mFocusTextBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
160 | //抗锯齿,对边界锯齿进行模糊处理
161 | mFocusTextBgPaint.setAntiAlias(true);
162 | mFocusTextBgPaint.setFilterBitmap(true);
163 | //防抖动,对图像抖动进行模糊平滑处理,使之看起来更柔和
164 | mFocusTextBgPaint.setDither(true);
165 | //实心填充绘制
166 | mFocusTextBgPaint.setStyle(Paint.Style.FILL);
167 | mFocusTextBgPaint.setColor(mFocusBackgroundColor);
168 | }
169 |
170 | @Override
171 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
172 |
173 | int height = MeasureSpec.getSize(heightMeasureSpec);
174 | if (mValueList.size() > 0) {
175 | //计算SideBar的实际高度
176 | mCalViewHeight = (int) (((mValueList.size() - 1) * mTextPaint.getTextSize() + mFocusTextPaint.getTextSize())
177 | + (mValueList.size() + 1) * mTextSpace);
178 | }
179 |
180 | if (mCalViewHeight > height) {//实际高度超过可用高度
181 | mCalViewHeight = height;
182 | }
183 |
184 | super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mCalViewHeight, MeasureSpec.EXACTLY));
185 | }
186 |
187 | @Override
188 | protected void onDraw(Canvas canvas) {
189 |
190 | super.onDraw(canvas);
191 | if(mValueList.size() == 0) {
192 | return;
193 | }
194 | //计算每项的高度
195 | mItemHeight = ((float) getHeight()) / mValueList.size();
196 |
197 | //关于文字居中参考:http://www.jianshu.com/p/c2b720fa5877和http://blog.csdn.net/zly921112/article/details/50401976
198 | float radius = Math.min(getWidth() / 2, mItemHeight / 2);//选中状态时圆形背景半径
199 | for(int i = 0; i < mValueList.size(); i++) {
200 | if(mSelectPosition == i) {
201 | //计算文本垂直居中的基准线
202 | float baseline = mItemHeight / 2 + (mFocusTextPaint.getFontMetrics().descent - mFocusTextPaint.getFontMetrics().ascent) / 2
203 | - mFocusTextPaint.getFontMetrics().descent;
204 | canvas.drawCircle(getWidth() / 2, mItemHeight / 2 + mItemHeight * i, radius, mFocusTextBgPaint);
205 | canvas.drawText(mValueList.get(i), getWidth() / 2, baseline + mItemHeight * i, mFocusTextPaint);
206 | } else {
207 | float baseline = mItemHeight / 2 + (mTextPaint.getFontMetrics().descent - mTextPaint.getFontMetrics().ascent) / 2
208 | - mTextPaint.getFontMetrics().descent;
209 | canvas.drawText(mValueList.get(i), getWidth() / 2, baseline + mItemHeight * i, mTextPaint);
210 | }
211 | }
212 | }
213 |
214 | @Override
215 | public boolean onTouch(View v, MotionEvent event) {
216 |
217 | int touchPosition = getPositionForPointY(event.getY());
218 | if(touchPosition < 0 || touchPosition >= mValueList.size()) {
219 | return true;
220 | }
221 | if(mOnSideBarTouchListener != null) {
222 | mOnSideBarTouchListener.onSideBarTouch(v, event, touchPosition);
223 | }
224 | switch (event.getAction()) {
225 | case MotionEvent.ACTION_DOWN:
226 | case MotionEvent.ACTION_MOVE:
227 | if(touchPosition != mSelectPosition) {
228 | setSelectPosition(touchPosition);
229 | }
230 | break;
231 | }
232 | return true;
233 | }
234 |
235 | /**
236 | * 根据点击的y坐标计算得到当前选中的是哪个选项
237 | * @param pointY
238 | * @return 没选中则返回-1
239 | */
240 | private int getPositionForPointY(float pointY) {
241 |
242 | if(mValueList.size() <= 0) {
243 | return -1;
244 | }
245 | //根据手按下的纵坐标与每个选项的高度计算当前所在项的索引
246 | int position = (int) (pointY / mItemHeight);
247 | if(position < 0) {
248 | position = 0;
249 | } else if(position > mValueList.size() - 1) {
250 | position = mValueList.size() - 1;
251 | }
252 | return position;
253 | }
254 |
255 | public void setData(List list) {
256 |
257 | mValueList = list;
258 | mSelectPosition = 0;
259 | requestLayout();
260 | invalidate();
261 | }
262 |
263 | public void setOnSideBarTouchListener(OnSideBarTouchListener onSideBarTouchListener) {
264 |
265 | mOnSideBarTouchListener = onSideBarTouchListener;
266 | }
267 |
268 | public int getSelectPosition() {
269 |
270 | return mSelectPosition;
271 | }
272 |
273 | public void setSelectPosition(int selectPosition) {
274 |
275 | if(selectPosition != mSelectPosition) {
276 | mSelectPosition = selectPosition;
277 | invalidate();
278 | }
279 | }
280 |
281 | public int getPosition(String indexValue) {
282 |
283 | return mValueList.indexOf(indexValue);
284 | }
285 |
286 | public String getIndexValue(int position) {
287 |
288 | return mValueList.get(position);
289 | }
290 |
291 | public int getTextColor() {
292 |
293 | return mTextColor;
294 | }
295 |
296 | public void setTextColor(int textColor) {
297 |
298 | if(textColor != mTextColor) {
299 | mTextColor = textColor;
300 | mTextPaint.setColor(mTextColor);
301 | invalidate();
302 | }
303 | }
304 |
305 | public int getFocusTextColor() {
306 |
307 | return mFocusTextColor;
308 | }
309 |
310 | public void setFocusTextColor(int focusTextColor) {
311 |
312 | mFocusTextColor = focusTextColor;
313 | mFocusTextPaint.setColor(focusTextColor);
314 | invalidate();
315 | }
316 |
317 | public float getTextSize() {
318 |
319 | return mTextSize;
320 | }
321 |
322 | public void setTextSize(float textSize) {
323 |
324 | mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());
325 | mTextPaint.setTextSize(mTextSize);
326 | mFocusTextPaint.setTextSize(mTextSize);
327 | requestLayout();
328 | invalidate();
329 | }
330 |
331 | public float getTextSpace() {
332 |
333 | return mTextSpace;
334 | }
335 |
336 | public void setTextSpace(float textSpace) {
337 |
338 | mTextSpace = textSpace;
339 | requestLayout();
340 | invalidate();
341 | }
342 |
343 | public interface OnSideBarTouchListener {
344 |
345 | /**
346 | * @param v
347 | * @param event
348 | * @param touchPosition 当前touch的Item项的索引,值>=0,调用此方法时,selectPosition值还未更新
349 | */
350 | void onSideBarTouch(View v, MotionEvent event, int touchPosition);
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/adapter/IndexHeaderFooterAdapter.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.adapter;
2 |
3 | import cn.ittiger.indexlist.entity.BaseEntity;
4 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
5 | import cn.ittiger.indexlist.helper.ConvertHelper;
6 | import cn.ittiger.indexlist.listener.OnItemClickListener;
7 | import cn.ittiger.indexlist.listener.OnItemLongClickListener;
8 |
9 | import android.support.v7.widget.RecyclerView;
10 | import android.text.TextUtils;
11 | import android.view.ViewGroup;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * 列表头部自定义实体数据适配器
18 | * @author: laohu on 2016/12/21
19 | * @site: http://ittiger.cn
20 | */
21 | public abstract class IndexHeaderFooterAdapter {
22 | private String mIndexValue;
23 | private String mIndexName;
24 | private List mOriginalList;
25 | private int mItemType = -1;
26 | private List> mEntityList;
27 | private boolean mNormalView = false;
28 | private OnItemClickListener mOnItemClickListener;
29 | private OnItemLongClickListener mOnItemLongClickListener;
30 |
31 | public IndexHeaderFooterAdapter() {
32 |
33 | mNormalView = true;
34 | }
35 |
36 | public IndexHeaderFooterAdapter(String indexValue, String indexName, List list) {
37 |
38 | mIndexValue = indexValue;
39 | mIndexName = indexName;
40 | mOriginalList = list;
41 | }
42 |
43 | public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);
44 |
45 | public abstract void onBindViewHolder(RecyclerView.ViewHolder holder, int position, T itemData);
46 |
47 | void transfer(int itemType) {
48 |
49 | mItemType = itemType;
50 | //转换得到当前添加的Header数据
51 | mEntityList = ConvertHelper.transferHeaderFooterData(this, mItemType);
52 |
53 | if(isNormalView() == false && !TextUtils.isEmpty(getIndexName())) {
54 | //当前添加的Header为索引Header且要显示的索引标题不为空时才创建索引实体
55 | IndexStickyEntity indexEntity = ConvertHelper.createIndexEntity(getIndexValue(), getIndexName());
56 | mEntityList.add(0, indexEntity);
57 | }
58 | }
59 |
60 | public String getIndexValue() {
61 |
62 | return mIndexValue;
63 | }
64 |
65 | public String getIndexName() {
66 |
67 | return mIndexName;
68 | }
69 |
70 | public List getOriginalList() {
71 |
72 | return mOriginalList;
73 | }
74 |
75 | public boolean isNormalView() {
76 |
77 | return mNormalView;
78 | }
79 |
80 | /**
81 | * 添加到{@link cn.ittiger.indexlist.IndexStickyView}中之后才会有值,否则为长度为0的列表
82 | * @return
83 | */
84 | public List> getEntityList() {
85 |
86 | return mEntityList == null ? new ArrayList>(0) : mEntityList;
87 | }
88 |
89 | /**
90 | * 添加到{@link cn.ittiger.indexlist.IndexStickyView}中之后才会有值,否则为-1
91 | * @return
92 | */
93 | public int getItemType() {
94 |
95 | return mItemType;
96 | }
97 |
98 | public OnItemClickListener getOnItemClickListener() {
99 |
100 | return mOnItemClickListener;
101 | }
102 |
103 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
104 |
105 | mOnItemClickListener = onItemClickListener;
106 | }
107 |
108 | public OnItemLongClickListener getOnItemLongClickListener() {
109 |
110 | return mOnItemLongClickListener;
111 | }
112 |
113 | public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
114 |
115 | mOnItemLongClickListener = onItemLongClickListener;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/adapter/IndexStickyViewAdapter.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.adapter;
2 |
3 | import cn.ittiger.indexlist.IndexValueBus;
4 | import cn.ittiger.indexlist.ItemType;
5 | import cn.ittiger.indexlist.entity.BaseEntity;
6 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
7 | import cn.ittiger.indexlist.helper.ConvertHelper;
8 | import cn.ittiger.indexlist.listener.OnItemClickListener;
9 | import cn.ittiger.indexlist.listener.OnItemLongClickListener;
10 |
11 | import android.support.v7.widget.RecyclerView;
12 | import android.text.TextUtils;
13 | import android.util.SparseArray;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 |
17 | import java.util.ArrayList;
18 | import java.util.HashMap;
19 | import java.util.List;
20 | import java.util.Map;
21 |
22 | /**
23 | * {@link cn.ittiger.indexlist.IndexStickyView}的数据适配器
24 | * @author: laohu on 2016/12/17
25 | * @site: http://ittiger.cn
26 | */
27 | public abstract class IndexStickyViewAdapter extends RecyclerView.Adapter {
28 | /**
29 | * 用户要展示的原始数据实体
30 | */
31 | private List mOriginalList;
32 | /**
33 | * 列表中实际展示时用到的实体数据
34 | */
35 | private List> mList;
36 | /**
37 | * 索引值在列表中位置的映射关系
38 | */
39 | private Map mIndexValuePositionMap;
40 | /**
41 | * 索引值列表
42 | */
43 | private List mIndexValueList;
44 | private OnItemClickListener mOnItemClickListener;
45 | private OnItemLongClickListener mOnItemLongClickListener;
46 |
47 | //自定义头部
48 | private SparseArray> mIndexHeaderAdapters = new SparseArray<>(0);
49 | private List> mIndexHeaderList = new ArrayList<>();
50 | private Map mIndexHeaderValuePositionMap = new HashMap<>();
51 | private List mIndexHeaderValueList = new ArrayList<>();
52 |
53 | //自定义Footer
54 | private SparseArray> mIndexFooterAdapters = new SparseArray<>(0);
55 | private List> mIndexFooterList = new ArrayList<>();
56 | private Map mIndexFooterValuePositionMap = new HashMap<>();
57 | private List mIndexFooterValueList = new ArrayList<>();
58 |
59 | public IndexStickyViewAdapter(List originalList) {
60 |
61 | if(originalList == null) {
62 | throw new NullPointerException("originalList is null");
63 | }
64 | mOriginalList = originalList;
65 | transferOriginalData();
66 | }
67 |
68 | private void transferOriginalData() {
69 |
70 | ConvertHelper.ConvertResult convertResult = ConvertHelper.transfer(mOriginalList);
71 | mList = convertResult.getIndexStickyEntities();
72 | mIndexValuePositionMap = convertResult.getIndexValuePositionMap();
73 | mIndexValueList = convertResult.getIndexValueList();
74 | }
75 |
76 | /**
77 | * 获取列表实际展示数据
78 | * @return
79 | */
80 | public final List> getData() {
81 |
82 | return mList;
83 | }
84 |
85 | @Override
86 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
87 |
88 | RecyclerView.ViewHolder viewHolder;
89 | if(viewType == ItemType.ITEM_TYPE_INDEX) {//IndexSticky索引类型视图
90 | viewHolder = onCreateIndexViewHolder(parent);
91 | } else if(viewType == ItemType.ITEM_TYPE_CONTENT) {//IndexSticky索引类型视图正常Item内容视图
92 | viewHolder = onCreateContentViewHolder(parent);
93 | } else if(mIndexHeaderAdapters.indexOfKey(viewType) >= 0) {//自定义的头部视图
94 | viewHolder = mIndexHeaderAdapters.get(viewType).onCreateViewHolder(parent);
95 | } else if(mIndexFooterAdapters.indexOfKey(viewType) >= 0) {//自定义的Footer视图
96 | viewHolder = mIndexFooterAdapters.get(viewType).onCreateViewHolder(parent);
97 | } else {
98 | throw new IllegalStateException("don't support viewType:" + viewType);
99 | }
100 | addListener(viewHolder, viewType);
101 | return viewHolder;
102 | }
103 |
104 | @Override
105 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
106 |
107 | int viewType = getItemViewType(position);
108 | if(viewType == ItemType.ITEM_TYPE_INDEX) {//IndexSticky索引类型视图
109 | onBindIndexViewHolder(holder, position, getItem(position).getIndexName());
110 | } else if(viewType == ItemType.ITEM_TYPE_CONTENT) {//IndexSticky索引类型视图正常Item内容视图
111 | onBindContentViewHolder(holder, position, getItem(position).getOriginalData());
112 | } else if(mIndexHeaderAdapters.indexOfKey(viewType) >= 0) {//自定义的头部视图
113 | mIndexHeaderAdapters.get(viewType).onBindViewHolder(holder, position, getItem(position).getOriginalData());
114 | } else if(mIndexFooterAdapters.indexOfKey(viewType) >= 0) {//自定义的Footer视图
115 | mIndexFooterAdapters.get(viewType).onBindViewHolder(holder, position, getItem(position).getOriginalData());
116 | } else {
117 | throw new IllegalStateException("don't support viewType:" + viewType);
118 | }
119 | }
120 |
121 | private void addListener(final RecyclerView.ViewHolder viewHolder, final int viewType) {
122 |
123 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
124 | @Override
125 | public void onClick(View v) {
126 | int position = viewHolder.getAdapterPosition();
127 | IndexStickyEntity entity = getItem(position);
128 | if(viewType == ItemType.ITEM_TYPE_CONTENT) {//IndexSticky索引类型视图正常Item内容视图
129 | if(mOnItemClickListener != null) {
130 | mOnItemClickListener.onItemClick(viewHolder.itemView, position, entity.getOriginalData());
131 | }
132 | } else if(mIndexHeaderAdapters.indexOfKey(viewType) >= 0) {//自定义的头部视图
133 | IndexHeaderFooterAdapter adapter = mIndexHeaderAdapters.get(viewType);
134 | if(adapter.getOnItemClickListener() != null) {
135 | adapter.getOnItemClickListener().onItemClick(viewHolder.itemView, position, entity.getOriginalData());
136 | }
137 | } else if(mIndexFooterAdapters.indexOfKey(viewType) >= 0) {//自定义的Footer视图
138 | IndexHeaderFooterAdapter adapter = mIndexFooterAdapters.get(viewType);
139 | if(adapter.getOnItemClickListener() != null) {
140 | adapter.getOnItemClickListener().onItemClick(viewHolder.itemView, position, entity.getOriginalData());
141 | }
142 | }
143 | }
144 | });
145 |
146 | viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
147 | @Override
148 | public boolean onLongClick(View v) {
149 |
150 | int position = viewHolder.getAdapterPosition();
151 | IndexStickyEntity entity = getItem(position);
152 | if(viewType == ItemType.ITEM_TYPE_CONTENT) {//IndexSticky索引类型视图正常Item内容视图
153 | if(mOnItemLongClickListener != null) {
154 | mOnItemLongClickListener.onItemLongClick(viewHolder.itemView, position, entity.getOriginalData());
155 | }
156 | } else if(mIndexHeaderAdapters.indexOfKey(viewType) >= 0) {//自定义的头部视图
157 | IndexHeaderFooterAdapter adapter = mIndexHeaderAdapters.get(viewType);
158 | if(adapter.getOnItemLongClickListener() != null) {
159 | adapter.getOnItemLongClickListener().onItemLongClick(viewHolder.itemView, position, entity.getOriginalData());
160 | }
161 | } else if(mIndexFooterAdapters.indexOfKey(viewType) >= 0) {//自定义的Footer视图
162 | IndexHeaderFooterAdapter adapter = mIndexFooterAdapters.get(viewType);
163 | if(adapter.getOnItemLongClickListener() != null) {
164 | adapter.getOnItemLongClickListener().onItemLongClick(viewHolder.itemView, position, entity.getOriginalData());
165 | }
166 | }
167 | return false;
168 | }
169 | });
170 | }
171 |
172 | @Override
173 | public int getItemCount() {
174 |
175 | return mList.size() + mIndexHeaderList.size() + mIndexFooterList.size();
176 | }
177 |
178 | public final IndexStickyEntity getItem(int position) {
179 |
180 | if(isIndexHeaderPosition(position)) {
181 | return mIndexHeaderList.get(position);
182 | }
183 | if(isIndexFooterPosition(position)) {
184 | return mIndexFooterList.get(position - mList.size() - mIndexHeaderList.size());
185 | }
186 | return mList.get(position - mIndexHeaderList.size());
187 | }
188 |
189 | @Override
190 | public int getItemViewType(int position) {
191 |
192 | return getItem(position).getItemType();
193 | }
194 |
195 | /**
196 | * 获取索引值在列表中的位置
197 | * @param indexValue
198 | * @return
199 | */
200 | public final int getIndexValuePosition(String indexValue) {
201 |
202 | if(mIndexValuePositionMap.containsKey(indexValue)) {
203 | return mIndexValuePositionMap.get(indexValue);
204 | }
205 | return -1;
206 | }
207 |
208 | /**
209 | * 获取索引值列表
210 | * @return
211 | */
212 | public List getIndexValueList() {
213 |
214 | return mIndexValueList;
215 | }
216 |
217 | /**------------ 设置Header自定义索引数据项适配器 -------------------------**/
218 | public void addIndexHeaderAdapter(IndexHeaderFooterAdapter indexHeaderAdapter) {
219 |
220 | if(indexHeaderAdapter == null) {
221 | return;
222 | }
223 | //因为可能会添加多个Header,所以此处的ItemType按照Header个数进行变化
224 | indexHeaderAdapter.transfer(ItemType.ITEM_TYPE_INDEX_HEADER + mIndexHeaderAdapters.size());
225 |
226 | mIndexHeaderAdapters.put(indexHeaderAdapter.getItemType(), indexHeaderAdapter);
227 |
228 | int entitySize = indexHeaderAdapter.getEntityList().size();
229 | //更新索引值与位置的映射
230 | for(String indexValue : mIndexValuePositionMap.keySet()) {
231 | mIndexValuePositionMap.put(indexValue, mIndexValuePositionMap.get(indexValue) + entitySize);
232 | }
233 | //更新之前添加的Header索引值与位置的映射
234 | for(String indexValue : mIndexHeaderValuePositionMap.keySet()) {
235 | mIndexHeaderValuePositionMap.put(indexValue, mIndexHeaderValuePositionMap.get(indexValue) + entitySize);
236 | }
237 | //如果当前添加的Header索引值不为空则添加该索引到索引列表中
238 | if(!TextUtils.isEmpty(indexHeaderAdapter.getIndexValue())) {
239 | mIndexValueList.add(0, indexHeaderAdapter.getIndexValue());
240 | mIndexValuePositionMap.put(indexHeaderAdapter.getIndexValue(), 0);
241 | mIndexHeaderValueList.add(0, indexHeaderAdapter.getIndexValue());
242 | mIndexHeaderValuePositionMap.put(indexHeaderAdapter.getIndexValue(), 0);
243 | }
244 | mIndexHeaderList.addAll(0, indexHeaderAdapter.getEntityList());
245 | notifyDataSetChanged();
246 | }
247 |
248 | /**
249 | * 更新列表后,重新将Header添加到列表中
250 | */
251 | private void addAllIndexHeaderAdapterData() {
252 |
253 | if(mIndexHeaderAdapters.size() > 0) {
254 | for(String indexValue : mIndexValuePositionMap.keySet()) {
255 | mIndexValuePositionMap.put(indexValue, mIndexValuePositionMap.get(indexValue) + mIndexHeaderList.size());
256 | }
257 | mIndexValuePositionMap.putAll(mIndexHeaderValuePositionMap);
258 | mIndexValueList.addAll(0, mIndexHeaderValueList);
259 | }
260 | }
261 |
262 | /**
263 | * 当前位置是否为Header区域
264 | * @param position
265 | * @return
266 | */
267 | private boolean isIndexHeaderPosition(int position) {
268 |
269 | return position < mIndexHeaderList.size();
270 | }
271 |
272 | /**------------ 设置Footer自定义索引数据项适配器 -------------------------**/
273 | public void addIndexFooterAdapter(IndexHeaderFooterAdapter indexFooterAdapter) {
274 |
275 | if(indexFooterAdapter == null) {
276 | return;
277 | }
278 | indexFooterAdapter.transfer(ItemType.ITEM_TYPE_INDEX_FOOTER + mIndexFooterAdapters.size());
279 | mIndexFooterAdapters.put(indexFooterAdapter.getItemType(), indexFooterAdapter);
280 |
281 | if(!TextUtils.isEmpty(indexFooterAdapter.getIndexValue())) {
282 | mIndexValueList.add(indexFooterAdapter.getIndexValue());
283 | mIndexValuePositionMap.put(indexFooterAdapter.getIndexValue(), getItemCount());
284 | mIndexFooterValueList.add(indexFooterAdapter.getIndexValue());
285 | mIndexFooterValuePositionMap.put(indexFooterAdapter.getIndexValue(), getItemCount());
286 | }
287 | mIndexFooterList.addAll(indexFooterAdapter.getEntityList());
288 | notifyDataSetChanged();
289 | }
290 |
291 | /**
292 | * 更新数据列表后,重新将Footer添加到列表中
293 | * @param count
294 | */
295 | private void addAllIndexFooterAdapterData(int count) {
296 |
297 | if(mIndexFooterAdapters.size() > 0) {
298 | for(String indexValue : mIndexFooterValuePositionMap.keySet()) {
299 | mIndexFooterValuePositionMap.put(indexValue, mIndexFooterValuePositionMap.get(indexValue) + count);
300 | }
301 | mIndexValuePositionMap.putAll(mIndexFooterValuePositionMap);
302 | mIndexValueList.addAll(mIndexFooterValueList);
303 | }
304 | }
305 |
306 | /**
307 | * 当前位置是否为Footer区域
308 | * @param position
309 | * @return
310 | */
311 | private boolean isIndexFooterPosition(int position) {
312 |
313 | return position >= mList.size() + mIndexHeaderList.size();
314 | }
315 |
316 |
317 | /**------------- 动态添加数据 -----------------**/
318 |
319 | public final void add(T originalEntity) {
320 |
321 | mOriginalList.add(originalEntity);
322 | refresh(1);
323 | }
324 |
325 | public final void add(List originalList) {
326 |
327 | mOriginalList.addAll(originalList);
328 | refresh(originalList.size());
329 | }
330 |
331 | public final void reset(List originalList) {
332 |
333 | if(originalList == null) {
334 | return;
335 | }
336 | int size = mOriginalList.size();
337 | mOriginalList = originalList;
338 | refresh(-size + originalList.size());
339 | }
340 |
341 | public final void remove(T originalEntity) {
342 |
343 | mOriginalList.remove(originalEntity);
344 | refresh(-1);
345 | }
346 |
347 | public final void remove(List originalList) {
348 |
349 | mOriginalList.removeAll(originalList);
350 | refresh(-originalList.size());
351 | }
352 |
353 | /**
354 | * 删除所有要显示的数据,不包括Header和Footer
355 | */
356 | public final void removeAll() {
357 |
358 | int size = mOriginalList.size();
359 | mOriginalList.clear();
360 | refresh(-size);
361 | }
362 |
363 | /**
364 | * 清空所有数据,包括Header和Footer
365 | */
366 | public final void clear() {
367 |
368 | mOriginalList.clear();
369 | removeAllFooter(false);
370 | removeAllHeader(false);
371 | refresh(0);
372 | }
373 |
374 | /**
375 | * 删除所有Header数据
376 | */
377 | public final void removeAllHeader() {
378 |
379 | removeAllHeader(true);
380 | }
381 |
382 | /**
383 | * 移除某个Header
384 | * @param adapter
385 | */
386 | public final void removeHeader(IndexHeaderFooterAdapter adapter) {
387 |
388 | int idx = mIndexHeaderAdapters.indexOfKey(adapter.getItemType());//当前删除的是第几个Header
389 | if(idx < 0) {
390 | return;
391 | }
392 | if(!TextUtils.isEmpty(adapter.getIndexValue())) {//移除当前要删除的Header的索引位置映射关系
393 | mIndexHeaderValuePositionMap.remove(adapter.getIndexValue());
394 | mIndexValuePositionMap.remove(adapter.getIndexValue());
395 |
396 | mIndexHeaderValueList.remove(adapter.getIndexValue());
397 | mIndexValueList.remove(adapter.getIndexValue());
398 | }
399 | int size = adapter.getEntityList().size();
400 | String indexValue;
401 | for(int i = idx + 1; i < mIndexHeaderAdapters.size(); i++) {//更新其他Header的索引位置映射关系
402 | indexValue = mIndexHeaderAdapters.valueAt(i).getIndexValue();
403 | if(!TextUtils.isEmpty(indexValue)) {
404 | int position = mIndexHeaderValuePositionMap.get(indexValue);
405 | mIndexHeaderValuePositionMap.put(indexValue, position - size);
406 | }
407 | }
408 | for(String key : mIndexFooterValuePositionMap.keySet()) {//更新Footer的索引位置映射关系
409 | int position = mIndexFooterValuePositionMap.get(key);
410 | mIndexFooterValuePositionMap.put(key, position - size);
411 | }
412 | for(String key : mIndexValuePositionMap.keySet()) {//更新所有数据的索引位置映射关系
413 | int position = mIndexValuePositionMap.get(key) - size;
414 | if(mIndexHeaderValuePositionMap.containsKey(key)) {
415 | position = mIndexHeaderValuePositionMap.get(key);
416 | } else if(mIndexFooterValuePositionMap.containsKey(key)) {
417 | position = mIndexFooterValuePositionMap.get(key);
418 | }
419 | mIndexValuePositionMap.put(key, position);
420 | }
421 | mIndexHeaderList.removeAll(adapter.getEntityList());
422 | mIndexHeaderAdapters.removeAt(idx);
423 | IndexValueBus.getInstance().notifyDataSetChanged(mIndexValueList);
424 | notifyDataSetChanged();
425 | }
426 |
427 | /**
428 | * 删除所有Header数据
429 | */
430 | private void removeAllHeader(boolean isRefresh) {
431 |
432 | if(mIndexHeaderAdapters.size() > 0) {
433 | for(String indexValue : mIndexHeaderValuePositionMap.keySet()) {
434 | mIndexValuePositionMap.remove(indexValue);
435 | }
436 | mIndexHeaderValuePositionMap.clear();
437 |
438 | mIndexValueList.removeAll(mIndexHeaderValueList);
439 | mIndexHeaderValueList.clear();
440 |
441 | mIndexHeaderAdapters.clear();
442 | mIndexHeaderList.clear();
443 |
444 | if(isRefresh) {
445 | refresh(0);
446 | }
447 | }
448 | }
449 |
450 | /**
451 | * 删除所有Footer
452 | */
453 | public final void removeAllFooter() {
454 |
455 | removeAllFooter(true);
456 | }
457 |
458 | /**
459 | * 移除某个Footer
460 | * @param adapter
461 | */
462 | public final void removeFooter(IndexHeaderFooterAdapter adapter) {
463 |
464 | int idx = mIndexFooterAdapters.indexOfKey(adapter.getItemType());//当前删除的是第几个Footer
465 | if(idx < 0) {
466 | return;
467 | }
468 | if(!TextUtils.isEmpty(adapter.getIndexValue())) {//移除当前要删除的Footer的索引位置映射关系
469 | mIndexFooterValuePositionMap.remove(adapter.getIndexValue());
470 | mIndexValuePositionMap.remove(adapter.getIndexValue());
471 |
472 | mIndexFooterValueList.remove(adapter.getIndexValue());
473 | mIndexValueList.remove(adapter.getIndexValue());
474 | }
475 | int size = adapter.getEntityList().size();
476 | String indexValue;
477 | for(int i = idx + 1; i < mIndexFooterAdapters.size(); i++) {//更新其他Footer的索引位置映射关系
478 | indexValue = mIndexFooterAdapters.valueAt(i).getIndexValue();
479 | if(!TextUtils.isEmpty(indexValue)) {
480 | int position = mIndexFooterValuePositionMap.get(indexValue);
481 | mIndexFooterValuePositionMap.put(indexValue, position - size);
482 | mIndexValuePositionMap.put(indexValue, position - size);
483 | }
484 | }
485 | mIndexFooterList.removeAll(adapter.getEntityList());
486 | mIndexFooterAdapters.removeAt(idx);
487 | IndexValueBus.getInstance().notifyDataSetChanged(mIndexValueList);
488 | notifyDataSetChanged();
489 | }
490 |
491 | /**
492 | * 删除所有Footer
493 | */
494 | private final void removeAllFooter(boolean isRefresh) {
495 |
496 | if(mIndexHeaderAdapters.size() > 0) {
497 | for(String indexValue : mIndexFooterValuePositionMap.keySet()) {
498 | mIndexValuePositionMap.remove(indexValue);
499 | }
500 | mIndexFooterValuePositionMap.clear();
501 |
502 | mIndexValueList.removeAll(mIndexFooterValueList);
503 | mIndexHeaderValueList.clear();
504 |
505 | mIndexFooterAdapters.clear();
506 | mIndexFooterList.clear();
507 |
508 | if(isRefresh) {
509 | refresh(0);
510 | }
511 | }
512 | }
513 |
514 | /**
515 | * 刷新数据列表
516 | * @param count
517 | */
518 | private void refresh(int count) {
519 |
520 | transferOriginalData();
521 | addAllIndexHeaderAdapterData();
522 | addAllIndexFooterAdapterData(count);
523 | IndexValueBus.getInstance().notifyDataSetChanged(mIndexValueList);
524 | notifyDataSetChanged();
525 | }
526 |
527 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
528 |
529 | mOnItemClickListener = onItemClickListener;
530 | }
531 |
532 | public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
533 |
534 | mOnItemLongClickListener = onItemLongClickListener;
535 | }
536 |
537 | /**------------ 待实现方法 ------------------**/
538 |
539 | public abstract RecyclerView.ViewHolder onCreateIndexViewHolder(ViewGroup parent);
540 |
541 | public abstract RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent);
542 |
543 | public abstract void onBindIndexViewHolder(RecyclerView.ViewHolder holder, int position, String indexName);
544 |
545 | public abstract void onBindContentViewHolder(RecyclerView.ViewHolder holder, int position, T itemData);
546 |
547 |
548 | }
549 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/entity/BaseEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.entity;
2 |
3 | /**
4 | * 你想要展示的数据实体基类
5 | * @author: laohu on 2016/12/17
6 | * @site: http://ittiger.cn
7 | */
8 | public interface BaseEntity {
9 | /**
10 | * 要索引的字段数据信息,例如联系人中对姓名进行索引,则此处返回姓名字段值
11 | * @return
12 | */
13 | String getIndexField();
14 | }
15 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/entity/IndexStickyEntity.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.entity;
2 |
3 | import cn.ittiger.indexlist.ItemType;
4 |
5 | /**
6 | * {@link RecyclerView}中展示的数据实体类
7 | * @author: laohu on 2016/12/17
8 | * @site: http://ittiger.cn
9 | */
10 | public class IndexStickyEntity {
11 |
12 | /**
13 | * 当前数据项的类型,自动转换赋值
14 | */
15 | private int mItemType = ItemType.ITEM_TYPE_CONTENT;
16 | /**
17 | * 当前数据的索引值,自动转换赋值
18 | */
19 | private String mIndexValue;
20 | /**
21 | * 索引视图显示的索引名称
22 | */
23 | private String mIndexName;
24 | /**
25 | * 原始数据,用户实际展示的数据
26 | * 当次值为null时,则表示此实体代表一个Index数据
27 | */
28 | private T mOriginalData;
29 | /**
30 | * 当前数据项的拼音
31 | */
32 | private String mPinYin;
33 |
34 | public int getItemType() {
35 |
36 | return mItemType;
37 | }
38 |
39 | public void setItemType(int itemType) {
40 |
41 | mItemType = itemType;
42 | }
43 |
44 | public String getIndexValue() {
45 |
46 | return mIndexValue;
47 | }
48 |
49 | public void setIndexValue(String indexValue) {
50 |
51 | mIndexValue = indexValue;
52 | }
53 |
54 | public T getOriginalData() {
55 |
56 | return mOriginalData;
57 | }
58 |
59 | public void setOriginalData(T originalData) {
60 |
61 | mOriginalData = originalData;
62 | }
63 |
64 | public String getPinYin() {
65 |
66 | return mPinYin;
67 | }
68 |
69 | public void setPinYin(String pinYin) {
70 |
71 | mPinYin = pinYin;
72 | }
73 |
74 | public String getIndexName() {
75 |
76 | return mIndexName;
77 | }
78 |
79 | public void setIndexName(String indexName) {
80 |
81 | mIndexName = indexName;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/helper/ComparatorFactory.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.helper;
2 |
3 | import cn.ittiger.indexlist.ItemType;
4 | import cn.ittiger.indexlist.entity.BaseEntity;
5 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
6 |
7 | import java.util.Comparator;
8 |
9 | import static cn.ittiger.indexlist.helper.ConvertHelper.INDEX_SPECIAL;
10 |
11 | /**
12 | * 比较器工厂
13 | * @author: laohu on 2016/12/21
14 | * @site: http://ittiger.cn
15 | */
16 | public class ComparatorFactory {
17 |
18 | /**
19 | * 索引值比较器
20 | * @return
21 | */
22 | public static Comparator indexValueComparator() {
23 |
24 | return new Comparator() {
25 | @Override
26 | public int compare(String lhs, String rhs) {
27 |
28 | return compareIndexValue(lhs, rhs);
29 | }
30 | };
31 | }
32 |
33 | /**
34 | * 列表实体比较器
35 | * @param
36 | * @return
37 | */
38 | public static Comparator> indexEntityComparator() {
39 |
40 | return new Comparator>() {
41 | @Override
42 | public int compare(IndexStickyEntity lhs, IndexStickyEntity rhs) {
43 |
44 | int value;
45 | if(lhs.getItemType() == ItemType.ITEM_TYPE_CONTENT && rhs.getItemType() == ItemType.ITEM_TYPE_CONTENT) {
46 | value = compareIndexEntity(lhs, rhs);
47 | } else {
48 | //只要有一个为Index类型,则只需要比较两者的索引值即可
49 | value = compareIndexValue(lhs.getIndexValue(), rhs.getIndexValue());
50 | }
51 | return value;
52 | }
53 | };
54 | }
55 |
56 | /**
57 | * 比较索引值大小
58 | * @param lhs
59 | * @param rhs
60 | * @return
61 | */
62 | private static int compareIndexValue(String lhs, String rhs) {
63 |
64 | if (INDEX_SPECIAL.equals(lhs)) {//索引值排序时#排在前面
65 | return INDEX_SPECIAL.equals(rhs) ? 0 : -1;
66 | } else if (INDEX_SPECIAL.equals(rhs)) {
67 | return 1;
68 | }
69 | return lhs.compareTo(rhs);
70 | }
71 |
72 | /**
73 | * 比较两个实体大小,且lhs为INDEX类型
74 | * @param lhs
75 | * @param rhs
76 | * @return
77 | */
78 | private static int compareIndexEntity(IndexStickyEntity lhs, IndexStickyEntity rhs) {
79 |
80 | String lhsIndexValue = lhs.getIndexValue();
81 | String rhsIndexValue = rhs.getIndexValue();
82 |
83 | if(!INDEX_SPECIAL.equals(lhsIndexValue) && !INDEX_SPECIAL.equals(rhsIndexValue)) {
84 | //两者的索引值均不为:#,则比较时只需要按照pinyin比较自然大小即可
85 | return lhs.getPinYin().compareTo(rhs.getPinYin());
86 | } else if(!INDEX_SPECIAL.equals(lhsIndexValue) && !INDEX_SPECIAL.equals(rhsIndexValue)) {
87 | //两者索引值均为:#,则比较时按照pinyin比较自然大小即可
88 | return lhs.getPinYin().compareTo(rhs.getPinYin());
89 | } else {
90 | //两者中只有一个索引值为:#,则索引值为#的小
91 | return INDEX_SPECIAL.equals(lhsIndexValue) ? -1 : 1;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/helper/ConvertHelper.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.helper;
2 |
3 | import cn.ittiger.indexlist.ItemType;
4 | import cn.ittiger.indexlist.adapter.IndexHeaderFooterAdapter;
5 | import cn.ittiger.indexlist.entity.BaseEntity;
6 | import cn.ittiger.indexlist.entity.IndexStickyEntity;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.TreeMap;
14 |
15 | /**
16 | * 实体类转换器
17 | * 将用户要显示的数据转换成{@link android.support.v7.widget.RecyclerView}中展示的实体
18 | * @author: laohu on 2016/12/17
19 | * @site: http://ittiger.cn
20 | */
21 | public class ConvertHelper {
22 | /**
23 | * 转换过程中,如果待索引字段信息为非字母串,则将其索引值设为:#
24 | */
25 | public static final String INDEX_SPECIAL = "#";
26 |
27 | public static class ConvertResult {
28 | //转换后得到的实际展示数据列表,包括联系人数据+组名称数据(索引名称)
29 | private List> mIndexStickyEntities = new ArrayList<>();
30 | //索引条中展示的数据列表
31 | private List mIndexValueList = new ArrayList<>();
32 | //索引条中展示数据与对应组在列表中位置索引的一一映射
33 | private Map mIndexValuePositionMap = new HashMap<>();
34 |
35 | public List> getIndexStickyEntities() {
36 |
37 | return mIndexStickyEntities;
38 | }
39 |
40 | public List getIndexValueList() {
41 |
42 | return mIndexValueList;
43 | }
44 |
45 | public Map getIndexValuePositionMap() {
46 |
47 | return mIndexValuePositionMap;
48 | }
49 | }
50 |
51 | public static ConvertResult transfer(List list) {
52 |
53 | ConvertResult convertResult = new ConvertResult();
54 |
55 | //使用TreeMap自动按照Key(字母索引值)进行排序
56 | TreeMap>> treeMap = new TreeMap<>(ComparatorFactory.indexValueComparator());
57 | for(int i = 0; i < list.size(); i++) {
58 | IndexStickyEntity entity = originalEntityToIndexEntity(list.get(i));
59 |
60 | if(treeMap.containsKey(entity.getIndexValue())) {//Map中已存在此索引值
61 | treeMap.get(entity.getIndexValue()).add(entity);
62 | } else {
63 | List> indexStickyEntities = new ArrayList<>();
64 | indexStickyEntities.add(entity);
65 | treeMap.put(entity.getIndexValue(), indexStickyEntities);
66 | }
67 | }
68 | for(String indexValue : treeMap.keySet()) {
69 | IndexStickyEntity indexValueEntity = createIndexEntity(indexValue, indexValue);
70 |
71 | //将索引值添加到索引值列表中
72 | convertResult.getIndexValueList().add(indexValue);
73 | //按顺序将索引实体添加到列表中
74 | convertResult.getIndexStickyEntities().add(indexValueEntity);
75 | //将索引值与索引值在结果列表中的位置进行映射
76 | convertResult.getIndexValuePositionMap().put(indexValue, convertResult.getIndexStickyEntities().size() - 1);
77 |
78 | //得到当前索引值下的索引数据实体
79 | List> indexStickyEntities = treeMap.get(indexValue);
80 | //对数据实体按自然进行排序
81 | Collections.sort(indexStickyEntities, ComparatorFactory.indexEntityComparator());
82 | //将排序后的实体列表按顺序加入到结果列表中
83 | convertResult.getIndexStickyEntities().addAll(indexStickyEntities);
84 | }
85 |
86 | return convertResult;
87 | }
88 |
89 | /**
90 | * 原始数据转换成展示的索引数据
91 | * @param originalEntity
92 | * @param
93 | * @return
94 | */
95 | public static IndexStickyEntity originalEntityToIndexEntity(T originalEntity) {
96 |
97 | IndexStickyEntity entity = new IndexStickyEntity<>();
98 | T item = originalEntity;
99 | String indexFieldName = item.getIndexField();
100 | String pinyin = PinYinHelper.getPingYin(indexFieldName);
101 | String indexValue = pinyin.substring(0, 1).toUpperCase();
102 | if(!PinYinHelper.isLetter(indexValue)) {//首字符如果非字母以#代替
103 | indexValue = INDEX_SPECIAL;
104 | }
105 | entity.setPinYin(pinyin);
106 | entity.setOriginalData(item);
107 | entity.setIndexValue(indexValue);
108 | entity.setIndexName(indexValue);
109 | return entity;
110 | }
111 |
112 | /**
113 | * 根据索引值创建索引实体对象
114 | * @param indexValue
115 | * @param
116 | * @return
117 | */
118 | public static IndexStickyEntity createIndexEntity(String indexValue, String indexName) {
119 |
120 | //根据索引值创建索引实体对象
121 | IndexStickyEntity indexValueEntity = new IndexStickyEntity<>();
122 | indexValueEntity.setIndexValue(indexValue);
123 | indexValueEntity.setPinYin(indexValue);
124 | indexValueEntity.setIndexName(indexName);
125 | indexValueEntity.setItemType(ItemType.ITEM_TYPE_INDEX);
126 | return indexValueEntity;
127 | }
128 |
129 | public static List> transferHeaderFooterData(IndexHeaderFooterAdapter adapter, int itemType) {
130 |
131 | List> entityList = new ArrayList<>();
132 | if(adapter.isNormalView()) {
133 | IndexStickyEntity entity = new IndexStickyEntity();
134 | entity.setPinYin("");
135 | entity.setIndexValue("");
136 | entity.setItemType(itemType);
137 | entityList.add(entity);
138 | } else {
139 | for(int i = 0; i < adapter.getOriginalList().size(); i++) {
140 | IndexStickyEntity entity = new IndexStickyEntity<>();
141 | T item = adapter.getOriginalList().get(i);
142 | String indexFieldName = item.getIndexField();
143 | String pinyin = PinYinHelper.getPingYin(indexFieldName);
144 | entity.setPinYin(pinyin);
145 | entity.setOriginalData(item);
146 | entity.setIndexValue(adapter.getIndexValue());
147 | entity.setIndexName(adapter.getIndexName());
148 | entity.setItemType(itemType);
149 | entityList.add(entity);
150 | }
151 | Collections.sort(entityList, ComparatorFactory.indexEntityComparator());
152 | }
153 | return entityList;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/helper/PinYinHelper.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.helper;
2 |
3 | import com.github.promeg.pinyinhelper.Pinyin;
4 |
5 | import java.util.regex.Pattern;
6 |
7 | /**
8 | * @author: laohu on 2016/12/17
9 | * @site: http://ittiger.cn
10 | */
11 | public class PinYinHelper {
12 | private static final String PATTERN_LETTER = "^[a-zA-Z]+$";
13 |
14 | /**
15 | * 将中文转换为pinyin
16 | */
17 | public static String getPingYin(String inputString) {
18 |
19 | char[] input = inputString.trim().toCharArray();
20 | String output = "";
21 | for (int i = 0; i < input.length; i++) {
22 | output += Pinyin.toPinyin(input[i]);
23 | }
24 | return output.toLowerCase();
25 | }
26 |
27 | /**
28 | * 是否为字母
29 | * @param inputString
30 | * @return
31 | */
32 | public static boolean isLetter(String inputString) {
33 |
34 | return Pattern.matches(PATTERN_LETTER, inputString);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/listener/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * @author: laohu on 2016/12/17
7 | * @site: http://ittiger.cn
8 | */
9 | public interface OnItemClickListener {
10 | /**
11 | * @param childView 点击选中的子视图
12 | * @param position 点击视图的索引
13 | * @param item 点击视图的类型
14 | */
15 | void onItemClick(View childView, int position, T item);
16 | }
17 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/java/cn/ittiger/indexlist/listener/OnItemLongClickListener.java:
--------------------------------------------------------------------------------
1 | package cn.ittiger.indexlist.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * @author: laohu on 2016/12/17
7 | * @site: http://ittiger.cn
8 | */
9 | public interface OnItemLongClickListener {
10 |
11 | /**
12 | * @param childView 点击选中的子视图
13 | * @param position 点击视图的索引
14 | * @param item 点击视图的类型
15 | */
16 | void onItemLongClick(View childView, int position, T item);
17 | }
18 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/res/drawable/indexstickyview_center_overlay_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/indexstickyview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':indexstickyview'
2 |
--------------------------------------------------------------------------------