├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── mcxtzhang
│ │ └── itemdecorationdemo
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── mcxtzhang
│ │ │ └── itemdecorationdemo
│ │ │ ├── adapter
│ │ │ ├── CityAdapter.java
│ │ │ └── MeituanAdapter.java
│ │ │ ├── decoration
│ │ │ └── DividerItemDecoration.java
│ │ │ ├── model
│ │ │ ├── CityBean.java
│ │ │ ├── MeiTuanBean.java
│ │ │ ├── MeituanHeaderBean.java
│ │ │ └── MeituanTopHeaderBean.java
│ │ │ ├── ui
│ │ │ ├── LauncherActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── MeituanSelectCityActivity.java
│ │ │ ├── SwipeDelMenuActivity.java
│ │ │ └── WeChatActivity.java
│ │ │ └── utils
│ │ │ ├── CommonAdapter.java
│ │ │ ├── HeaderRecyclerAndFooterWrapperAdapter.java
│ │ │ ├── OnItemClickListener.java
│ │ │ └── ViewHolder.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── friend.png
│ │ └── group.png
│ │ ├── drawable
│ │ ├── meituan_iten_header_item_bg.xml
│ │ └── shape_side_bar_bg.xml
│ │ ├── layout
│ │ ├── activity_launcher.xml
│ │ ├── activity_main.xml
│ │ ├── activity_meituan.xml
│ │ ├── header_complex.xml
│ │ ├── item_city.xml
│ │ ├── item_city_swipe.xml
│ │ ├── meituan_item_header.xml
│ │ ├── meituan_item_header_item.xml
│ │ ├── meituan_item_header_top.xml
│ │ └── meituan_item_select_city.xml
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── mcxtzhang
│ └── itemdecorationdemo
│ └── ExampleUnitTest.java
├── build.gradle
├── gif
├── ItemDecorationIndexBar_SwipeDel.gif
├── citylist
├── meituan.gif
└── weixin.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── indexlib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── mcxtzhang
│ │ └── indexlib
│ │ ├── IndexBar
│ │ ├── bean
│ │ │ ├── BaseIndexBean.java
│ │ │ └── BaseIndexPinyinBean.java
│ │ ├── helper
│ │ │ ├── IIndexBarDataHelper.java
│ │ │ └── IndexBarDataHelperImpl.java
│ │ └── widget
│ │ │ └── IndexBar.java
│ │ └── suspension
│ │ ├── ISuspensionInterface.java
│ │ └── SuspensionDecoration.java
│ └── res
│ └── 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 | /.idea/*
10 | # OSX
11 |
12 | *.DS_Store
13 |
14 |
15 | # Gradle files
16 | build/
17 | .gradle/
18 | */build/
19 |
20 |
21 | # IDEA
22 | *.iml
23 | .idea/.name
24 | .idea/encodings.xml
25 | .idea/inspectionProfiles/Project_Default.xml
26 | .idea/inspectionProfiles/profiles_settings.xml
27 | .idea/misc.xml
28 | .idea/modules.xml
29 | .idea/scopes/scope_settings.xml
30 | .idea/vcs.xml
31 | .idea/workspace.xml
32 | .idea/libraries
33 |
34 |
35 | # Built application files
36 | *.apk
37 | *.ap_
38 |
39 |
40 | # Files for the Dalvik VM
41 | *.dex
42 |
43 |
44 | # Java class files
45 | *.class
46 |
47 |
48 | # Generated files
49 | antLauncher/bin
50 | antLauncher/gen
51 |
52 |
53 | # Local configuration file (sdk path, etc)
54 | local.properties
55 |
56 |
57 | # Log Files
58 | *.log
59 |
60 | .idea
61 | .gradle
62 | /local.properties
63 | /.idea/workspace.xml
64 | /.idea/libraries
65 | .DS_Store
66 | /build
67 | /captures
68 | *.iml
69 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | ItemDecorationIndexBar
--------------------------------------------------------------------------------
/.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/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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.8
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SuspensionIndexBar
2 | 相关博文:
3 |
4 | 从0实现:
5 | http://blog.csdn.net/zxt0601/article/details/52355199
6 |
7 | http://blog.csdn.net/zxt0601/article/details/52420706
8 |
9 | 使用说明 and 封装:
10 | http://gold.xitu.io/post/583c133eac502e006c23cc81
11 |
12 | 喜欢随手点个star 多谢
13 |
14 | ## 在哪里找到我:
15 | 我的github:
16 |
17 | https://github.com/mcxtzhang
18 |
19 | 我的CSDN博客:
20 |
21 | http://blog.csdn.net/zxt0601
22 |
23 | 我的稀土掘金:
24 |
25 | http://gold.xitu.io/user/56de210b816dfa0052e66495
26 |
27 | 我的简书:
28 |
29 | http://www.jianshu.com/users/8e91ff99b072/timeline
30 |
31 | # 效果一览:
32 | 配合我另一个库组装的效果(SuspensionIndexBar + SwipeMenuLayout):
33 |
34 | (SwipeDelMenuLayout : https://github.com/mcxtzhang/SwipeDelMenuLayout)
35 |
36 | SwipeDelMenuActivity:
37 | 
38 |
39 | 高仿美团选择城市界面(MeituanSelectCityActivity):
40 |
41 | 
42 |
43 | 高仿微信通讯录界面(WeChatActivity):
44 |
45 | 
46 |
47 | 普通城市列表界面(MainActivity):
48 |
49 | 
50 |
51 | # 引入
52 | Step 1. Add the JitPack repository to your build file
53 | Add it in your root build.gradle at the end of repositories:
54 | ```
55 | allprojects {
56 | repositories {
57 | ...
58 | maven { url 'https://jitpack.io' }
59 | }
60 | }
61 | ```
62 | Step 2. Add the dependency
63 | ```
64 | dependencies {
65 | compile 'com.github.mcxtzhang:SuspensionIndexBar:V1.0.0'
66 | }
67 | ```
68 |
69 | # 使用说明:
70 | http://gold.xitu.io/post/583c133eac502e006c23cc81
71 |
72 |
73 | # 更新记录:
74 | 2016 11 29 :
75 | * 重构悬停分组,将TitleItemDecoration更名为SuspensionDecoration,数据源依赖ISuspensionInterface接口。
76 | * 重构索引导航,将IndexBar对数据源的操作,如排序,转拼音等分离出去,以接口IIndexBarDataHelper通信。
77 | * 有N多兄弟给我留言、加QQ问的:如何实现美团选择城市列表页面,
78 | * 添加一个不带悬停分组的HeaderView(微信通讯录界面)
79 |
80 | 2016 11 10 :
81 | 1 IndexBar也考虑了HeaderView不需要索引的情况:
82 | ```
83 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
84 | .setNeedRealIndex(true)//设置需要真实的索引
85 | .setmLayoutManager(mManager)//设置RecyclerView的LayoutManager
86 | .setmSourceDatas(mDatas)//设置数据
87 | .setHeaderViewCount(mWrapperAdapter.getHeaderViewCount());//设置HeaderView数量
88 | ```
89 |
90 | 2016 11 07 :
91 | 1 考虑了HeaderView不需要索引的情况,TitleItemDecoration增加了一个headerView的字段和方法:
92 | ```
93 | new TitleItemDecoration(this, mDatas).setHeaderViewCount(mWrapperAdapter.getHeaderViewCount())
94 | ```
95 | 2 暂时抽离TitleItemDecoration至lib包,并且以 **接口形式接收数据** 。
96 |
97 |
98 | # to do list
99 | 1 扩展 SuspensionDecoration 支持传入layout or View。
100 |
101 |
102 | # 闲言碎语
103 |
104 | ### 一
105 | 网上关于实现带悬停分组头部的列表的方法有很多,像我看过有主席的自定义ExpandListView实现的,也看过有人用一个额外的父布局里面套 RecyclerView/ListView+一个头部View(位置固定在父布局上方)实现的。
106 | 对于以上解决方案,有以下几点个人觉得不好的地方:
107 |
108 | 1. 现在RecyclerView是主流
109 |
110 | 2. 在RecyclerView外套一个父布局总归是**增加布局层级,容易overdraw**,显得不够优雅。
111 |
112 | 3. item布局实现带这种分类头部的方法有两种,一种是把分类头部当做一种itemViewtype(麻烦),另一种是每个Item布局都包含了分类头部的布局,代码里根据postion等信息动态Visible,Gone头部(布局冗余,item效率降低)。
113 |
114 | 况且Google为我们提供了**ItemDecoration**,它本身就是用来修饰RecyclerView里的Item的,它的```getItemOffsets() onDraw()```方法用于为Item分类头部留出空间和绘制(解决缺点3),它的```onDrawOver()```方法用于绘制悬停的头部View(解决缺点2)。
115 |
116 | 而且更重要的是,**ItemDecoration出来这么久了,你还不用它**?
117 |
118 | 本文就利用ItemDecoration 打造 分组列表,并配有悬停头部功能。
119 |
120 |
121 | 亮点预览:**添加多个ItemDecoration、它们的执行顺序、ItemDecoration方法执行顺序、ItemDecoration和RecyclerView的绘制顺序**
122 |
123 | (http://blog.csdn.net/zxt0601/article/details/52355199)
124 |
125 | ### 二
126 |
127 | 我们用ItemDecoration为RecyclerView打造了带悬停头部的分组列表。其实Android版微信的通讯录界面,它的分组title也不是悬停的,我们已经领先了微信一小步(认真脸)~
128 | 再看看市面上常见的分组列表(例如饿了么点餐商品列表),不仅有悬停头部,悬停头部在切换时,还会伴有**切换动画**。
129 | 关于ItemDecoration还有一个问题,简单布局还好,我们可以draw出来,如果是复杂的头部呢?能否写个xml,inflate进来,这样使用起来才简单,即另一种**简单使用onDraw和onDrawOver**的姿势。
130 | so,本文开头我们就先用两节完善一下我们的ItemDecoration。然后进入正题:自定义View实现右侧索引导航栏IndexBar,对数据源的排序字段按照拼音排序,最后将RecyclerView和IndexBar联动起来,触摸IndexBar上相应字母,RecyclerView滚动到相应位置。(在屏幕中间显示的其实就是一个TextView,我们set个体IndexBar即可)
131 | 由于大部分使用右侧索引导航栏的场景,都需要这几个固定步骤,对数据源排序,set给IndexBar,和RecyclerView联动等,所以最后再将其封装一把,成一个高度封装,因此扩展性不太高的控件,更方便使用,如果需要扩展的话,反正看完本文再其基础上修改应该很简单~。
132 |
133 | 本文摘要:
134 | 1. 用ItemDecoration实现悬停头部**切换动画**
135 | 2. 另一种**简单使用onDraw()和onDrawOver()**的姿势
136 | 3. 自定义View实现右侧**索引导航栏**IndexBar
137 | 4. 使用TinyPinyin对数据源排序
138 | 5. 联动IndexBar和RecyclerView。
139 | 6. 封装重复步骤,方便二次使用,并可**定制导航数据源**。
140 |
141 | (http://blog.csdn.net/zxt0601/article/details/52420706)
142 |
143 | ### 三
144 |
145 | 本文是这个系列的第三篇,不出意外也是终结篇。因为使用经过重构后的控件已经可以快速实现市面上带 索引导航、悬停分组的列表界面了。
146 | 在前两篇里,我们从0开始,一步一步实现了仿微信通讯录、饿了么选餐界面。
147 | (第一篇戳我 第二篇戳我)
148 | 这篇文章作为终结篇,和前文相比,主要涉及以下内容:
149 |
150 | * 重构悬停分组,将TitleItemDecoration更名为SuspensionDecoration,数据源依赖ISuspensionInterface接口。
151 | * 重构索引导航,将IndexBar对数据源的操作,如排序,转拼音等分离出去,以接口IIndexBarDataHelper通信。
152 | * 有N多兄弟给我留言、加QQ问的:如何实现美团选择城市列表页面,
153 | * 添加一个不带悬停分组的HeaderView(微信通讯录界面)
154 |
155 | http://gold.xitu.io/post/583c133eac502e006c23cc81
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.mcxtzhang.itemdecorationindexbar"
9 | minSdkVersion 14
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "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 fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:24.2.1'
26 | compile project (':indexlib')
27 | compile 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.2.2'
28 | }
29 |
--------------------------------------------------------------------------------
/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 C:\Users\admin\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/mcxtzhang/itemdecorationdemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("mcxtzhang.itemdecorationdemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/adapter/CityAdapter.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 | import android.widget.Toast;
11 |
12 | import java.util.List;
13 |
14 | import mcxtzhang.itemdecorationdemo.model.CityBean;
15 | import mcxtzhang.itemdecorationdemo.R;
16 |
17 | /**
18 | * Created by zhangxutong .
19 | * Date: 16/08/28
20 | */
21 |
22 | public class CityAdapter extends RecyclerView.Adapter {
23 | protected Context mContext;
24 | protected List mDatas;
25 | protected LayoutInflater mInflater;
26 |
27 | public CityAdapter(Context mContext, List mDatas) {
28 | this.mContext = mContext;
29 | this.mDatas = mDatas;
30 | mInflater = LayoutInflater.from(mContext);
31 | }
32 |
33 | public List getDatas() {
34 | return mDatas;
35 | }
36 |
37 | public CityAdapter setDatas(List datas) {
38 | mDatas = datas;
39 | return this;
40 | }
41 |
42 | @Override
43 | public CityAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
44 | return new ViewHolder(mInflater.inflate(R.layout.item_city, parent, false));
45 | }
46 |
47 | @Override
48 | public void onBindViewHolder(final CityAdapter.ViewHolder holder, final int position) {
49 | final CityBean cityBean = mDatas.get(position);
50 | holder.tvCity.setText(cityBean.getCity());
51 | holder.content.setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | Toast.makeText(mContext, "pos:" + position, Toast.LENGTH_SHORT).show();
55 | }
56 | });
57 | holder.avatar.setImageResource(R.drawable.friend);
58 | }
59 |
60 | @Override
61 | public int getItemCount() {
62 | return mDatas != null ? mDatas.size() : 0;
63 | }
64 |
65 | public static class ViewHolder extends RecyclerView.ViewHolder {
66 | TextView tvCity;
67 | ImageView avatar;
68 | View content;
69 |
70 | public ViewHolder(View itemView) {
71 | super(itemView);
72 | tvCity = (TextView) itemView.findViewById(R.id.tvCity);
73 | avatar = (ImageView) itemView.findViewById(R.id.ivAvatar);
74 | content = itemView.findViewById(R.id.content);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/adapter/MeituanAdapter.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.adapter;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.List;
6 |
7 | import mcxtzhang.itemdecorationdemo.R;
8 | import mcxtzhang.itemdecorationdemo.model.MeiTuanBean;
9 | import mcxtzhang.itemdecorationdemo.utils.CommonAdapter;
10 | import mcxtzhang.itemdecorationdemo.utils.ViewHolder;
11 |
12 | /**
13 | * Created by zhangxutong .
14 | * Date: 16/08/28
15 | */
16 |
17 | public class MeituanAdapter extends CommonAdapter {
18 | public MeituanAdapter(Context context, int layoutId, List datas) {
19 | super(context, layoutId, datas);
20 | }
21 |
22 | @Override
23 | public void convert(ViewHolder holder, final MeiTuanBean cityBean) {
24 | holder.setText(R.id.tvCity, cityBean.getCity());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/decoration/DividerItemDecoration.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.decoration;/*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * limitations under the License.
6 | */
7 |
8 | import android.content.Context;
9 | import android.content.res.TypedArray;
10 | import android.graphics.Canvas;
11 | import android.graphics.Rect;
12 | import android.graphics.drawable.Drawable;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.view.View;
16 |
17 |
18 | /**
19 | * This class is from the v7 samples of the Android SDK. It's not by me!
20 | *
21 | * See the license above for details.
22 | */
23 | public class DividerItemDecoration extends RecyclerView.ItemDecoration {
24 |
25 | private static final int[] ATTRS = new int[]{
26 | android.R.attr.listDivider
27 | };
28 |
29 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
30 |
31 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
32 |
33 | protected Drawable mDivider;
34 |
35 | private int mOrientation;
36 |
37 | public DividerItemDecoration(Context context, int orientation) {
38 | final TypedArray a = context.obtainStyledAttributes(ATTRS);
39 | mDivider = a.getDrawable(0);
40 | a.recycle();
41 | setOrientation(orientation);
42 | }
43 |
44 | public void setOrientation(int orientation) {
45 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
46 | throw new IllegalArgumentException("invalid orientation");
47 | }
48 | mOrientation = orientation;
49 | }
50 |
51 | @Override
52 | public void onDraw(Canvas c, RecyclerView parent) {
53 |
54 | if (mOrientation == VERTICAL_LIST) {
55 | drawVertical(c, parent);
56 | } else {
57 | drawHorizontal(c, parent);
58 | }
59 |
60 | }
61 |
62 |
63 | public void drawVertical(Canvas c, RecyclerView parent) {
64 | final int left = parent.getPaddingLeft();
65 | final int right = parent.getWidth() - parent.getPaddingRight();
66 |
67 | final int childCount = parent.getChildCount();
68 | for (int i = 0; i < childCount; i++) {
69 | final View child = parent.getChildAt(i);
70 | //RecyclerView v = new RecyclerView(parent.getContext());
71 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
72 | .getLayoutParams();
73 | final int top = child.getBottom() + params.bottomMargin;
74 | final int bottom = top + mDivider.getIntrinsicHeight();
75 | mDivider.setBounds(left, top, right, bottom);
76 | mDivider.draw(c);
77 | }
78 | }
79 |
80 | public void drawHorizontal(Canvas c, RecyclerView parent) {
81 | final int top = parent.getPaddingTop();
82 | final int bottom = parent.getHeight() - parent.getPaddingBottom();
83 |
84 | final int childCount = parent.getChildCount();
85 | for (int i = 0; i < childCount; i++) {
86 | final View child = parent.getChildAt(i);
87 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
88 | .getLayoutParams();
89 | final int left = child.getRight() + params.rightMargin;
90 | final int right = left + mDivider.getIntrinsicHeight();
91 | mDivider.setBounds(left, top, right, bottom);
92 | mDivider.draw(c);
93 | }
94 | }
95 |
96 | @Override
97 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
98 | if (mOrientation == VERTICAL_LIST) {
99 | outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
100 | } else {
101 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/model/CityBean.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.model;
2 |
3 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
4 |
5 | /**
6 | * Created by zhangxutong .
7 | * Date: 16/08/28
8 | */
9 |
10 | public class CityBean extends BaseIndexPinyinBean {
11 |
12 | private String city;//城市名字
13 | private boolean isTop;//是否是最上面的 不需要被转化成拼音的
14 |
15 | public CityBean() {
16 | }
17 |
18 | public CityBean(String city) {
19 | this.city = city;
20 | }
21 |
22 | public String getCity() {
23 | return city;
24 | }
25 |
26 | public CityBean setCity(String city) {
27 | this.city = city;
28 | return this;
29 | }
30 |
31 | public boolean isTop() {
32 | return isTop;
33 | }
34 |
35 | public CityBean setTop(boolean top) {
36 | isTop = top;
37 | return this;
38 | }
39 |
40 | @Override
41 | public String getTarget() {
42 | return city;
43 | }
44 |
45 | @Override
46 | public boolean isNeedToPinyin() {
47 | return !isTop;
48 | }
49 |
50 |
51 | @Override
52 | public boolean isShowSuspension() {
53 | return !isTop;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/model/MeiTuanBean.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.model;
2 |
3 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
4 |
5 | /**
6 | * 介绍:美团里的城市bean
7 | * 作者:zhangxutong
8 | * 邮箱:mcxtzhang@163.com
9 | * 主页:http://blog.csdn.net/zxt0601
10 | * 时间: 2016/11/28.
11 | */
12 |
13 | public class MeiTuanBean extends BaseIndexPinyinBean {
14 | private String city;//城市名字
15 |
16 | public MeiTuanBean() {
17 | }
18 |
19 | public MeiTuanBean(String city) {
20 | this.city = city;
21 | }
22 |
23 | public String getCity() {
24 | return city;
25 | }
26 |
27 | public MeiTuanBean setCity(String city) {
28 | this.city = city;
29 | return this;
30 | }
31 |
32 | @Override
33 | public String getTarget() {
34 | return city;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/model/MeituanHeaderBean.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.model;
2 |
3 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * 介绍:美团城市列表 HeaderView Bean
9 | * 作者:zhangxutong
10 | * 邮箱:mcxtzhang@163.com
11 | * 主页:http://blog.csdn.net/zxt0601
12 | * 时间: 2016/11/28.
13 | */
14 |
15 | public class MeituanHeaderBean extends BaseIndexPinyinBean {
16 | private List cityList;
17 | //悬停ItemDecoration显示的Tag
18 | private String suspensionTag;
19 |
20 | public MeituanHeaderBean() {
21 | }
22 |
23 | public MeituanHeaderBean(List cityList, String suspensionTag, String indexBarTag) {
24 | this.cityList = cityList;
25 | this.suspensionTag = suspensionTag;
26 | this.setBaseIndexTag(indexBarTag);
27 | }
28 |
29 | public List getCityList() {
30 | return cityList;
31 | }
32 |
33 | public MeituanHeaderBean setCityList(List cityList) {
34 | this.cityList = cityList;
35 | return this;
36 | }
37 |
38 | public MeituanHeaderBean setSuspensionTag(String suspensionTag) {
39 | this.suspensionTag = suspensionTag;
40 | return this;
41 | }
42 |
43 | @Override
44 | public String getTarget() {
45 | return null;
46 | }
47 |
48 | @Override
49 | public boolean isNeedToPinyin() {
50 | return false;
51 | }
52 |
53 | @Override
54 | public String getSuspensionTag() {
55 | return suspensionTag;
56 | }
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/model/MeituanTopHeaderBean.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.model;
2 |
3 | /**
4 | * 介绍:美团最顶部Header
5 | * 作者:zhangxutong
6 | * 邮箱:mcxtzhang@163.com
7 | * CSDN:http://blog.csdn.net/zxt0601
8 | * 时间: 16/11/28.
9 | */
10 |
11 | public class MeituanTopHeaderBean {
12 | private String txt;
13 |
14 | public MeituanTopHeaderBean(String txt) {
15 | this.txt = txt;
16 | }
17 |
18 | public String getTxt() {
19 | return txt;
20 | }
21 |
22 | public MeituanTopHeaderBean setTxt(String txt) {
23 | this.txt = txt;
24 | return this;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/ui/LauncherActivity.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 |
8 | import mcxtzhang.itemdecorationdemo.R;
9 |
10 | public class LauncherActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_launcher);
16 | findViewById(R.id.wx).setOnClickListener(new View.OnClickListener() {
17 | @Override
18 | public void onClick(View v) {
19 | startActivity(new Intent(v.getContext(), WeChatActivity.class));
20 | }
21 | });
22 |
23 | findViewById(R.id.main).setOnClickListener(new View.OnClickListener() {
24 | @Override
25 | public void onClick(View v) {
26 | startActivity(new Intent(v.getContext(), MainActivity.class));
27 | }
28 | });
29 |
30 | findViewById(R.id.swipe).setOnClickListener(new View.OnClickListener() {
31 | @Override
32 | public void onClick(View v) {
33 | startActivity(new Intent(v.getContext(), SwipeDelMenuActivity.class));
34 | }
35 | });
36 |
37 | findViewById(R.id.meituanCityList).setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | startActivity(new Intent(v.getContext(), MeituanSelectCityActivity.class));
41 | }
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.ui;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 | import android.widget.TextView;
9 |
10 | import com.mcxtzhang.indexlib.IndexBar.widget.IndexBar;
11 | import com.mcxtzhang.indexlib.suspension.SuspensionDecoration;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import mcxtzhang.itemdecorationdemo.R;
17 | import mcxtzhang.itemdecorationdemo.adapter.CityAdapter;
18 | import mcxtzhang.itemdecorationdemo.decoration.DividerItemDecoration;
19 | import mcxtzhang.itemdecorationdemo.model.CityBean;
20 | import mcxtzhang.itemdecorationdemo.utils.HeaderRecyclerAndFooterWrapperAdapter;
21 | import mcxtzhang.itemdecorationdemo.utils.ViewHolder;
22 |
23 | public class MainActivity extends Activity {
24 | private static final String TAG = "zxt";
25 | private RecyclerView mRv;
26 | private CityAdapter mAdapter;
27 | private HeaderRecyclerAndFooterWrapperAdapter mHeaderAdapter;
28 | private LinearLayoutManager mManager;
29 | private List mDatas;
30 |
31 | private SuspensionDecoration mDecoration;
32 |
33 | /**
34 | * 右侧边栏导航区域
35 | */
36 | private IndexBar mIndexBar;
37 |
38 | /**
39 | * 显示指示器DialogText
40 | */
41 | private TextView mTvSideBarHint;
42 |
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | setContentView(R.layout.activity_main);
48 |
49 | mRv = (RecyclerView) findViewById(R.id.rv);
50 | mRv.setLayoutManager(mManager = new LinearLayoutManager(this));
51 |
52 | mAdapter = new CityAdapter(this, mDatas);
53 | mHeaderAdapter = new HeaderRecyclerAndFooterWrapperAdapter(mAdapter) {
54 | @Override
55 | protected void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, Object o) {
56 | holder.setText(R.id.tvCity, (String) o);
57 | }
58 | };
59 | mHeaderAdapter.setHeaderView(R.layout.item_city, "测试头部");
60 |
61 | mRv.setAdapter(mHeaderAdapter);
62 | mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mDatas).setHeaderViewCount(mHeaderAdapter.getHeaderViewCount()));
63 |
64 | //如果add两个,那么按照先后顺序,依次渲染。
65 | mRv.addItemDecoration(new DividerItemDecoration(MainActivity.this, DividerItemDecoration.VERTICAL_LIST));
66 |
67 | //使用indexBar
68 | mTvSideBarHint = (TextView) findViewById(R.id.tvSideBarHint);//HintTextView
69 | mIndexBar = (IndexBar) findViewById(R.id.indexBar);//IndexBar
70 |
71 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
72 | .setNeedRealIndex(true)//设置需要真实的索引
73 | .setmLayoutManager(mManager);//设置RecyclerView的LayoutManager
74 |
75 | initDatas(getResources().getStringArray(R.array.provinces));
76 | }
77 |
78 | /**
79 | * 组织数据源
80 | *
81 | * @param data
82 | * @return
83 | */
84 | private void initDatas(final String[] data) {
85 | //延迟200ms 模拟加载数据中....
86 | getWindow().getDecorView().postDelayed(new Runnable() {
87 | @Override
88 | public void run() {
89 | mDatas = new ArrayList<>();
90 | for (int i = 0; i < data.length; i++) {
91 | CityBean cityBean = new CityBean();
92 | cityBean.setCity(data[i]);//设置城市名称
93 | mDatas.add(cityBean);
94 | }
95 |
96 | mIndexBar.setmSourceDatas(mDatas)//设置数据
97 | .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount())//设置HeaderView数量
98 | .invalidate();
99 |
100 | mAdapter.setDatas(mDatas);
101 | mHeaderAdapter.notifyDataSetChanged();
102 | mDecoration.setmDatas(mDatas);
103 | }
104 | }, 200);
105 |
106 | }
107 |
108 | /**
109 | * 更新数据源
110 | *
111 | * @param view
112 | */
113 | public void updateDatas(View view) {
114 | for (int i = 0; i < 5; i++) {
115 | mDatas.add(new CityBean("东京"));
116 | mDatas.add(new CityBean("大阪"));
117 | }
118 | mIndexBar.setmSourceDatas(mDatas)
119 | .invalidate();
120 | mHeaderAdapter.notifyDataSetChanged();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/ui/MeituanSelectCityActivity.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.ui;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.GridLayoutManager;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.util.TypedValue;
10 | import android.view.View;
11 | import android.widget.TextView;
12 | import android.widget.Toast;
13 |
14 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
15 | import com.mcxtzhang.indexlib.IndexBar.widget.IndexBar;
16 | import com.mcxtzhang.indexlib.suspension.SuspensionDecoration;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import mcxtzhang.itemdecorationdemo.R;
22 | import mcxtzhang.itemdecorationdemo.adapter.MeituanAdapter;
23 | import mcxtzhang.itemdecorationdemo.decoration.DividerItemDecoration;
24 | import mcxtzhang.itemdecorationdemo.model.MeiTuanBean;
25 | import mcxtzhang.itemdecorationdemo.model.MeituanHeaderBean;
26 | import mcxtzhang.itemdecorationdemo.model.MeituanTopHeaderBean;
27 | import mcxtzhang.itemdecorationdemo.utils.CommonAdapter;
28 | import mcxtzhang.itemdecorationdemo.utils.HeaderRecyclerAndFooterWrapperAdapter;
29 | import mcxtzhang.itemdecorationdemo.utils.ViewHolder;
30 |
31 |
32 | /**
33 | * 介绍: 高仿美团选择城市页面
34 | * 作者:zhangxutong
35 | * 邮箱:mcxtzhang@163.com
36 | * 主页:http://blog.csdn.net/zxt0601
37 | * 时间: 2016/11/7.
38 | */
39 | public class MeituanSelectCityActivity extends AppCompatActivity {
40 | private static final String TAG = "zxt";
41 | private Context mContext;
42 | private RecyclerView mRv;
43 | private MeituanAdapter mAdapter;
44 | private HeaderRecyclerAndFooterWrapperAdapter mHeaderAdapter;
45 | private LinearLayoutManager mManager;
46 |
47 | //设置给InexBar、ItemDecoration的完整数据集
48 | private List mSourceDatas;
49 | //头部数据源
50 | private List mHeaderDatas;
51 | //主体部分数据源(城市数据)
52 | private List mBodyDatas;
53 |
54 | private SuspensionDecoration mDecoration;
55 |
56 | /**
57 | * 右侧边栏导航区域
58 | */
59 | private IndexBar mIndexBar;
60 |
61 | /**
62 | * 显示指示器DialogText
63 | */
64 | private TextView mTvSideBarHint;
65 |
66 |
67 | @Override
68 | protected void onCreate(Bundle savedInstanceState) {
69 | super.onCreate(savedInstanceState);
70 | setContentView(R.layout.activity_meituan);
71 | mContext = this;
72 |
73 | mRv = (RecyclerView) findViewById(R.id.rv);
74 | mRv.setLayoutManager(mManager = new LinearLayoutManager(this));
75 |
76 | mSourceDatas = new ArrayList<>();
77 | mHeaderDatas = new ArrayList<>();
78 | List locationCity = new ArrayList<>();
79 | locationCity.add("定位中");
80 | mHeaderDatas.add(new MeituanHeaderBean(locationCity, "定位城市", "定"));
81 | List recentCitys = new ArrayList<>();
82 | mHeaderDatas.add(new MeituanHeaderBean(recentCitys, "最近访问城市", "近"));
83 | List hotCitys = new ArrayList<>();
84 | mHeaderDatas.add(new MeituanHeaderBean(hotCitys, "热门城市", "热"));
85 | mSourceDatas.addAll(mHeaderDatas);
86 |
87 | mAdapter = new MeituanAdapter(this, R.layout.meituan_item_select_city, mBodyDatas);
88 | mHeaderAdapter = new HeaderRecyclerAndFooterWrapperAdapter(mAdapter) {
89 | @Override
90 | protected void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, Object o) {
91 | switch (layoutId) {
92 | case R.layout.meituan_item_header:
93 | final MeituanHeaderBean meituanHeaderBean = (MeituanHeaderBean) o;
94 | //网格
95 | RecyclerView recyclerView = holder.getView(R.id.rvCity);
96 | recyclerView.setAdapter(
97 | new CommonAdapter(mContext, R.layout.meituan_item_header_item, meituanHeaderBean.getCityList()) {
98 | @Override
99 | public void convert(ViewHolder holder, final String cityName) {
100 | holder.setText(R.id.tvName, cityName);
101 | holder.getConvertView().setOnClickListener(new View.OnClickListener() {
102 | @Override
103 | public void onClick(View v) {
104 | Toast.makeText(mContext, "cityName:" + cityName, Toast.LENGTH_SHORT).show();
105 | }
106 | });
107 | }
108 | });
109 | recyclerView.setLayoutManager(new GridLayoutManager(mContext, 3));
110 | break;
111 | case R.layout.meituan_item_header_top:
112 | MeituanTopHeaderBean meituanTopHeaderBean = (MeituanTopHeaderBean) o;
113 | holder.setText(R.id.tvCurrent, meituanTopHeaderBean.getTxt());
114 | break;
115 | default:
116 | break;
117 | }
118 | }
119 | };
120 | mHeaderAdapter.setHeaderView(0, R.layout.meituan_item_header_top, new MeituanTopHeaderBean("当前:上海徐汇"));
121 | mHeaderAdapter.setHeaderView(1, R.layout.meituan_item_header, mHeaderDatas.get(0));
122 | mHeaderAdapter.setHeaderView(2, R.layout.meituan_item_header, mHeaderDatas.get(1));
123 | mHeaderAdapter.setHeaderView(3, R.layout.meituan_item_header, mHeaderDatas.get(2));
124 |
125 |
126 | mRv.setAdapter(mHeaderAdapter);
127 | mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mSourceDatas)
128 | .setmTitleHeight((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 35, getResources().getDisplayMetrics()))
129 | .setColorTitleBg(0xffefefef)
130 | .setTitleFontSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()))
131 | .setColorTitleFont(mContext.getResources().getColor(android.R.color.black))
132 | .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size()));
133 | mRv.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL_LIST));
134 |
135 | //使用indexBar
136 | mTvSideBarHint = (TextView) findViewById(R.id.tvSideBarHint);//HintTextView
137 | mIndexBar = (IndexBar) findViewById(R.id.indexBar);//IndexBar
138 |
139 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
140 | .setNeedRealIndex(true)//设置需要真实的索引
141 | .setmLayoutManager(mManager)//设置RecyclerView的LayoutManager
142 | .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size());
143 |
144 | initDatas(getResources().getStringArray(R.array.provinces));
145 | }
146 |
147 | /**
148 | * 组织数据源
149 | *
150 | * @param data
151 | * @return
152 | */
153 | private void initDatas(final String[] data) {
154 | //延迟两秒 模拟加载数据中....
155 | getWindow().getDecorView().postDelayed(new Runnable() {
156 | @Override
157 | public void run() {
158 | mBodyDatas = new ArrayList<>();
159 | for (int i = 0; i < data.length; i++) {
160 | MeiTuanBean cityBean = new MeiTuanBean();
161 | cityBean.setCity(data[i]);//设置城市名称
162 | mBodyDatas.add(cityBean);
163 | }
164 | //先排序
165 | mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);
166 |
167 | mAdapter.setDatas(mBodyDatas);
168 | mHeaderAdapter.notifyDataSetChanged();
169 | mSourceDatas.addAll(mBodyDatas);
170 |
171 | mIndexBar.setmSourceDatas(mSourceDatas)//设置数据
172 | .invalidate();
173 | mDecoration.setmDatas(mSourceDatas);
174 | }
175 | }, 1000);
176 |
177 | //延迟两秒加载头部
178 | getWindow().getDecorView().postDelayed(new Runnable() {
179 | @Override
180 | public void run() {
181 | MeituanHeaderBean header1 = mHeaderDatas.get(0);
182 | header1.getCityList().clear();
183 | header1.getCityList().add("上海");
184 |
185 | MeituanHeaderBean header2 = mHeaderDatas.get(1);
186 | List recentCitys = new ArrayList<>();
187 | recentCitys.add("日本");
188 | recentCitys.add("北京");
189 | header2.setCityList(recentCitys);
190 |
191 | MeituanHeaderBean header3 = mHeaderDatas.get(2);
192 | List hotCitys = new ArrayList<>();
193 | hotCitys.add("上海");
194 | hotCitys.add("北京");
195 | hotCitys.add("杭州");
196 | hotCitys.add("广州");
197 | header3.setCityList(hotCitys);
198 |
199 | mHeaderAdapter.notifyItemRangeChanged(1, 3);
200 |
201 | }
202 | }, 2000);
203 |
204 | }
205 |
206 | /**
207 | * 更新数据源
208 | *
209 | * @param view
210 | */
211 | public void updateDatas(View view) {
212 | for (int i = 0; i < 5; i++) {
213 | mBodyDatas.add(new MeiTuanBean("东京"));
214 | mBodyDatas.add(new MeiTuanBean("大阪"));
215 | }
216 | //先排序
217 | mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);
218 | mSourceDatas.clear();
219 | mSourceDatas.addAll(mHeaderDatas);
220 | mSourceDatas.addAll(mBodyDatas);
221 |
222 | mHeaderAdapter.notifyDataSetChanged();
223 | mIndexBar.invalidate();
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/ui/SwipeDelMenuActivity.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.ui;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | import com.mcxtzhang.indexlib.IndexBar.widget.IndexBar;
13 | import com.mcxtzhang.indexlib.suspension.SuspensionDecoration;
14 | import com.mcxtzhang.swipemenulib.SwipeMenuLayout;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import mcxtzhang.itemdecorationdemo.R;
20 | import mcxtzhang.itemdecorationdemo.adapter.CityAdapter;
21 | import mcxtzhang.itemdecorationdemo.decoration.DividerItemDecoration;
22 | import mcxtzhang.itemdecorationdemo.model.CityBean;
23 |
24 | /**
25 | * 介绍:组装SwipeDelMenu的Activity
26 | * (Activity不需要进行任何的修改 )
27 | * 和WeChatActivity一模一样
28 | * 作者:zhangxutong
29 | * 邮箱:mcxtzhang@163.com
30 | * 主页:http://blog.csdn.net/zxt0601
31 | * 时间: 2016/11/7.
32 | */
33 |
34 | public class SwipeDelMenuActivity extends AppCompatActivity {
35 | private static final String TAG = "zxt";
36 | private static final String INDEX_STRING_TOP = "↑";
37 | private RecyclerView mRv;
38 | private SwipeDelMenuAdapter mAdapter;
39 | private LinearLayoutManager mManager;
40 | private List mDatas = new ArrayList<>();
41 |
42 | private SuspensionDecoration mDecoration;
43 |
44 | /**
45 | * 右侧边栏导航区域
46 | */
47 | private IndexBar mIndexBar;
48 |
49 | /**
50 | * 显示指示器DialogText
51 | */
52 | private TextView mTvSideBarHint;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | setContentView(R.layout.activity_main);
58 |
59 | mRv = (RecyclerView) findViewById(R.id.rv);
60 | mRv.setLayoutManager(mManager = new LinearLayoutManager(this));
61 |
62 |
63 | mAdapter = new SwipeDelMenuAdapter(this, mDatas);
64 | mRv.setAdapter(mAdapter);
65 | mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mDatas));
66 | //如果add两个,那么按照先后顺序,依次渲染。
67 | //mRv.addItemDecoration(new TitleItemDecoration2(this,mDatas));
68 | mRv.addItemDecoration(new DividerItemDecoration(SwipeDelMenuActivity.this, DividerItemDecoration.VERTICAL_LIST));
69 |
70 |
71 | //使用indexBar
72 | mTvSideBarHint = (TextView) findViewById(R.id.tvSideBarHint);//HintTextView
73 | mIndexBar = (IndexBar) findViewById(R.id.indexBar);//IndexBar
74 |
75 | //模拟线上加载数据
76 | initDatas(getResources().getStringArray(R.array.provinces));
77 | }
78 |
79 | /**
80 | * 组织数据源
81 | *
82 | * @param data
83 | * @return
84 | */
85 | private void initDatas(final String[] data) {
86 | //延迟两秒 模拟加载数据中....
87 | getWindow().getDecorView().postDelayed(new Runnable() {
88 | @Override
89 | public void run() {
90 | mDatas = new ArrayList<>();
91 | //微信的头部 也是可以右侧IndexBar导航索引的,
92 | // 但是它不需要被ItemDecoration设一个标题titile
93 | mDatas.add((CityBean) new CityBean("新的朋友").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
94 | mDatas.add((CityBean) new CityBean("群聊").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
95 | mDatas.add((CityBean) new CityBean("标签").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
96 | mDatas.add((CityBean) new CityBean("公众号").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
97 | for (int i = 0; i < data.length; i++) {
98 | CityBean cityBean = new CityBean();
99 | cityBean.setCity(data[i]);//设置城市名称
100 | mDatas.add(cityBean);
101 | }
102 | mAdapter.setDatas(mDatas);
103 | mAdapter.notifyDataSetChanged();
104 |
105 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
106 | .setNeedRealIndex(true)//设置需要真实的索引
107 | .setmLayoutManager(mManager)//设置RecyclerView的LayoutManager
108 | .setmSourceDatas(mDatas)//设置数据
109 | .invalidate();
110 | mDecoration.setmDatas(mDatas);
111 | }
112 | }, 2000);
113 | }
114 |
115 | /**
116 | * 更新数据源
117 | *
118 | * @param view
119 | */
120 | public void updateDatas(View view) {
121 | for (int i = 0; i < 5; i++) {
122 | mDatas.add(new CityBean("东京"));
123 | mDatas.add(new CityBean("大阪"));
124 | }
125 | mIndexBar.setmSourceDatas(mDatas)
126 | .invalidate();
127 | mAdapter.notifyDataSetChanged();
128 |
129 | }
130 |
131 |
132 | /**
133 | * 和CityAdapter 一模一样,只是修改了 Item的布局
134 | * Created by zhangxutong .
135 | * Date: 16/08/28
136 | */
137 |
138 | private class SwipeDelMenuAdapter extends CityAdapter {
139 |
140 | public SwipeDelMenuAdapter(Context mContext, List mDatas) {
141 | super(mContext, mDatas);
142 | }
143 |
144 | @Override
145 | public SwipeDelMenuAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
146 | return new ViewHolder(mInflater.inflate(R.layout.item_city_swipe, parent, false));
147 | }
148 |
149 | @Override
150 | public void onBindViewHolder(final ViewHolder holder, int position) {
151 | super.onBindViewHolder(holder, position);
152 | holder.itemView.findViewById(R.id.btnDel).setOnClickListener(new View.OnClickListener() {
153 | @Override
154 | public void onClick(View v) {
155 | ((SwipeMenuLayout) holder.itemView).quickClose();
156 | mDatas.remove(holder.getAdapterPosition());
157 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
158 | .setNeedRealIndex(true)//设置需要真实的索引
159 | .setmLayoutManager(mManager)//设置RecyclerView的LayoutManager
160 | .setmSourceDatas(mDatas)//设置数据
161 | .invalidate();
162 | notifyDataSetChanged();
163 | }
164 | });
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/ui/WeChatActivity.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.ui;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 | import android.widget.TextView;
9 |
10 | import com.mcxtzhang.indexlib.IndexBar.widget.IndexBar;
11 | import com.mcxtzhang.indexlib.suspension.SuspensionDecoration;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import mcxtzhang.itemdecorationdemo.model.CityBean;
17 | import mcxtzhang.itemdecorationdemo.R;
18 | import mcxtzhang.itemdecorationdemo.adapter.CityAdapter;
19 | import mcxtzhang.itemdecorationdemo.decoration.DividerItemDecoration;
20 |
21 | /**
22 | * 介绍:高仿微信通讯录界面
23 | * 头部不是HeaderView 因为头部也需要快速导航,"↑"
24 | * 作者:zhangxutong
25 | * 邮箱:mcxtzhang@163.com
26 | * 主页:http://blog.csdn.net/zxt0601
27 | * 时间: 2016/11/7.
28 | */
29 |
30 | public class WeChatActivity extends AppCompatActivity {
31 | private static final String TAG = "zxt";
32 | private static final String INDEX_STRING_TOP = "↑";
33 | private RecyclerView mRv;
34 | private CityAdapter mAdapter;
35 | private LinearLayoutManager mManager;
36 | private List mDatas = new ArrayList<>();
37 |
38 | private SuspensionDecoration mDecoration;
39 |
40 | /**
41 | * 右侧边栏导航区域
42 | */
43 | private IndexBar mIndexBar;
44 |
45 | /**
46 | * 显示指示器DialogText
47 | */
48 | private TextView mTvSideBarHint;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | setContentView(R.layout.activity_main);
54 |
55 | mRv = (RecyclerView) findViewById(R.id.rv);
56 | mRv.setLayoutManager(mManager = new LinearLayoutManager(this));
57 |
58 | mAdapter = new CityAdapter(this, mDatas);
59 | mRv.setAdapter(mAdapter);
60 | mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mDatas));
61 | //如果add两个,那么按照先后顺序,依次渲染。
62 | mRv.addItemDecoration(new DividerItemDecoration(WeChatActivity.this, DividerItemDecoration.VERTICAL_LIST));
63 |
64 | //使用indexBar
65 | mTvSideBarHint = (TextView) findViewById(R.id.tvSideBarHint);//HintTextView
66 | mIndexBar = (IndexBar) findViewById(R.id.indexBar);//IndexBar
67 |
68 | //indexbar初始化
69 | mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView
70 | .setNeedRealIndex(true)//设置需要真实的索引
71 | .setmLayoutManager(mManager);//设置RecyclerView的LayoutManager
72 |
73 | //模拟线上加载数据
74 | initDatas(getResources().getStringArray(R.array.provinces));
75 | }
76 |
77 | /**
78 | * 组织数据源
79 | *
80 | * @param data
81 | * @return
82 | */
83 | private void initDatas(final String[] data) {
84 | //延迟两秒 模拟加载数据中....
85 | getWindow().getDecorView().postDelayed(new Runnable() {
86 | @Override
87 | public void run() {
88 | mDatas = new ArrayList<>();
89 | //微信的头部 也是可以右侧IndexBar导航索引的,
90 | // 但是它不需要被ItemDecoration设一个标题titile
91 | mDatas.add((CityBean) new CityBean("新的朋友").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
92 | mDatas.add((CityBean) new CityBean("群聊").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
93 | mDatas.add((CityBean) new CityBean("标签").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
94 | mDatas.add((CityBean) new CityBean("公众号").setTop(true).setBaseIndexTag(INDEX_STRING_TOP));
95 | for (int i = 0; i < data.length; i++) {
96 | CityBean cityBean = new CityBean();
97 | cityBean.setCity(data[i]);//设置城市名称
98 | mDatas.add(cityBean);
99 | }
100 | mAdapter.setDatas(mDatas);
101 | mAdapter.notifyDataSetChanged();
102 |
103 | mIndexBar.setmSourceDatas(mDatas)//设置数据
104 | .invalidate();
105 | mDecoration.setmDatas(mDatas);
106 | }
107 | }, 500);
108 | }
109 |
110 | /**
111 | * 更新数据源
112 | *
113 | * @param view
114 | */
115 | public void updateDatas(View view) {
116 | for (int i = 0; i < 5; i++) {
117 | mDatas.add(new CityBean("东京"));
118 | mDatas.add(new CityBean("大阪"));
119 | }
120 |
121 | mIndexBar.setmSourceDatas(mDatas)
122 | .invalidate();
123 | mAdapter.notifyDataSetChanged();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/utils/CommonAdapter.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.utils;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * 通用的RecyclerView 的Adapter
14 | * Created by zhangxutong .
15 | * Date: 16/03/11
16 | */
17 | public abstract class CommonAdapter extends RecyclerView.Adapter {
18 | protected Context mContext;
19 | protected int mLayoutId;
20 | protected List mDatas;
21 | protected LayoutInflater mInflater;
22 | protected ViewGroup mRv;//add by zhangxutong 2016 08 05 ,for 点击事件为了兼容HeaderView FooterView 的Adapter
23 |
24 | private OnItemClickListener mOnItemClickListener;
25 |
26 | public CommonAdapter setOnItemClickListener(OnItemClickListener onItemClickListener) {
27 | this.mOnItemClickListener = onItemClickListener;
28 | return this;
29 | }
30 |
31 | public OnItemClickListener getmOnItemClickListener() {
32 | return mOnItemClickListener;
33 | }
34 |
35 | public CommonAdapter(Context context, int layoutId, List datas) {
36 | mContext = context;
37 | mInflater = LayoutInflater.from(context);
38 | mLayoutId = layoutId;
39 | mDatas = datas;
40 | }
41 |
42 | @Override
43 | public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
44 | ViewHolder viewHolder = ViewHolder.get(mContext, null, parent, mLayoutId, -1);
45 | //add by zhangxutong 2016 08 05 begin ,for 点击事件为了兼容HeaderView FooterView 的Adapter
46 | if (null == mRv) {
47 | mRv = parent;
48 | }
49 | //setListener(parent, viewHolder, viewType);
50 | //add by zhangxutong 2016 08 05 end ,for 点击事件为了兼容HeaderView FooterView 的Adapter
51 | return viewHolder;
52 | }
53 |
54 | protected int getPosition(RecyclerView.ViewHolder viewHolder) {
55 | return viewHolder.getAdapterPosition();
56 | }
57 |
58 | protected boolean isEnabled(int viewType) {
59 | return true;
60 | }
61 |
62 |
63 | /**
64 | * 在onCreateHolder里调用的,但是在增加了HeaderFooter后,postion位置,会不正确。
65 | * 所以如果使用了{@link HeaderAndFooterWrapperAdapter},建议使用 {@link #setListener(int, ViewHolder)} 这个方法,返回的位置是正确的。
66 | *
67 | * @param parent
68 | * @param viewHolder
69 | * @param viewType
70 | */
71 | @Deprecated
72 | protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType) {
73 | if (!isEnabled(viewType)) return;
74 | viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {
75 | @Override
76 | public void onClick(View v) {
77 | if (mOnItemClickListener != null) {
78 | int position = getPosition(viewHolder);
79 | mOnItemClickListener.onItemClick(parent, v, mDatas.get(position), position);
80 | }
81 | }
82 | });
83 |
84 |
85 | viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {
86 | @Override
87 | public boolean onLongClick(View v) {
88 | if (mOnItemClickListener != null) {
89 | int position = getPosition(viewHolder);
90 | return mOnItemClickListener.onItemLongClick(parent, v, mDatas.get(position), position);
91 | }
92 | return false;
93 | }
94 | });
95 | }
96 |
97 | @Override
98 | public void onBindViewHolder(ViewHolder holder, int position) {
99 | holder.updatePosition(position);
100 | //add by zhangxutong 2016 08 05 begin 点击事件为了兼容HeaderView FooterView 的Adapter,所以在OnBindViewHolder里,其实性能没有onCreate好
101 | setListener(position, holder);
102 | //add by zhangxutong 2016 08 05 end
103 | convert(holder, mDatas.get(position));
104 | }
105 |
106 | //add by zhangxutong 2016 08 05 begin 点击事件为了兼容HeaderView FooterView 的Adapter,所以在OnBindViewHolder里,其实性能没有onCreate好
107 | protected void setListener(final int position, final ViewHolder viewHolder) {
108 | if (!isEnabled(getItemViewType(position))) return;
109 | viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {
110 | @Override
111 | public void onClick(View v) {
112 | if (mOnItemClickListener != null) {
113 | mOnItemClickListener.onItemClick(mRv, v, mDatas.get(position), position);
114 | }
115 | }
116 | });
117 |
118 |
119 | viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {
120 | @Override
121 | public boolean onLongClick(View v) {
122 | if (mOnItemClickListener != null) {
123 | int position = getPosition(viewHolder);
124 | return mOnItemClickListener.onItemLongClick(mRv, v, mDatas.get(position), position);
125 | }
126 | return false;
127 | }
128 | });
129 | }
130 |
131 | public abstract void convert(ViewHolder holder, T t);
132 |
133 | @Override
134 | public int getItemCount() {
135 | return mDatas != null ? mDatas.size() : 0;
136 | }
137 |
138 |
139 | /**
140 | * 刷新数据,初始化数据
141 | *
142 | * @param list
143 | */
144 | public void setDatas(List list) {
145 | if (this.mDatas != null) {
146 | if (null != list) {
147 | List temp = new ArrayList<>();
148 | temp.addAll(list);
149 | this.mDatas.clear();
150 | this.mDatas.addAll(temp);
151 | } else {
152 | this.mDatas.clear();
153 | }
154 | } else {
155 | this.mDatas = list;
156 | }
157 | notifyDataSetChanged();
158 | }
159 |
160 | /**
161 | * 删除数据
162 | *
163 | * @param i
164 | */
165 | public void remove(int i) {
166 | if (null != mDatas && mDatas.size() > i && i > -1) {
167 | mDatas.remove(i);
168 | notifyDataSetChanged();
169 | }
170 | }
171 |
172 | /**
173 | * 加载更多数据
174 | *
175 | * @param list
176 | */
177 | public void addDatas(List list) {
178 | if (null != list) {
179 | List temp = new ArrayList<>();
180 | temp.addAll(list);
181 | if (this.mDatas != null) {
182 | this.mDatas.addAll(temp);
183 | } else {
184 | this.mDatas = temp;
185 | }
186 | notifyDataSetChanged();
187 | }
188 |
189 | }
190 |
191 |
192 | public List getDatas() {
193 | return mDatas;
194 | }
195 |
196 |
197 | public T getItem(int position) {
198 | if (position > -1 && null != mDatas && mDatas.size() > position) {
199 | return mDatas.get(position);
200 | }
201 | return null;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/utils/HeaderRecyclerAndFooterWrapperAdapter.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.utils;
2 |
3 | import android.support.v4.util.SparseArrayCompat;
4 | import android.support.v7.widget.GridLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.support.v7.widget.StaggeredGridLayoutManager;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | /**
11 | * 介绍:一个给RecyclerView添加HeaderView FooterView的装饰Adapter类
12 | * 重点哦~ RecyclerView的HeaderView将可以被系统回收,不像老版的HeaderView是一个强引用在内存里
13 | * 作者:zhangxutong
14 | * 邮箱:zhangxutong@imcoming.com
15 | * 时间: 2016/8/2.
16 | */
17 | public abstract class HeaderRecyclerAndFooterWrapperAdapter extends RecyclerView.Adapter {
18 | private static final int BASE_ITEM_TYPE_HEADER = 1000000;//headerview的viewtype基准值
19 | private static final int BASE_ITEM_TYPE_FOOTER = 2000000;//footerView的ViewType基准值
20 |
21 | //存放HeaderViews的layoudID和data,key是viewType,value 是 layoudID和data,
22 | // 在createViewHOlder里根据layoutId创建UI,
23 | // 在onbindViewHOlder里依据这个data渲染UI,同时也将layoutId回传出去用于判断何种Header
24 | private SparseArrayCompat mHeaderDatas = new SparseArrayCompat();
25 | private SparseArrayCompat mFooterViews = new SparseArrayCompat<>();//存放FooterViews,key是viewType
26 |
27 | protected RecyclerView.Adapter mInnerAdapter;//内部的的普通Adapter
28 |
29 | public HeaderRecyclerAndFooterWrapperAdapter(RecyclerView.Adapter mInnerAdapter) {
30 | this.mInnerAdapter = mInnerAdapter;
31 | }
32 |
33 | public int getHeaderViewCount() {
34 | return mHeaderDatas.size();
35 | }
36 |
37 | public int getFooterViewCount() {
38 | return mFooterViews.size();
39 | }
40 |
41 | private int getInnerItemCount() {
42 | return mInnerAdapter != null ? mInnerAdapter.getItemCount() : 0;
43 | }
44 |
45 | /**
46 | * 传入position 判断是否是headerview
47 | *
48 | * @param position
49 | * @return
50 | */
51 | public boolean isHeaderViewPos(int position) {// 举例, 2 个头,pos 0 1,true, 2+ false
52 | return getHeaderViewCount() > position;
53 | }
54 |
55 | /**
56 | * 传入postion判断是否是footerview
57 | *
58 | * @param position
59 | * @return
60 | */
61 | public boolean isFooterViewPos(int position) {//举例, 2个头,2个inner,pos 0 1 2 3 ,false,4+true
62 | return position >= getHeaderViewCount() + getInnerItemCount();
63 | }
64 |
65 | /**
66 | * 添加HeaderView
67 | *
68 | * @param layoutId headerView 的LayoutId
69 | * @param data headerView 的data(可能多种不同类型的header 只能用Object了)
70 | */
71 | public void addHeaderView(int layoutId, Object data) {
72 | //mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, v);
73 | SparseArrayCompat headerContainer = new SparseArrayCompat();
74 | headerContainer.put(layoutId, data);
75 | mHeaderDatas.put(mHeaderDatas.size() + BASE_ITEM_TYPE_HEADER, headerContainer);
76 | }
77 |
78 | /**
79 | * 设置(更新)某个layoutId的HeaderView的数据
80 | *
81 | * @param layoutId
82 | * @param data
83 | */
84 | public void setHeaderView(int layoutId, Object data) {
85 | boolean isFinded = false;
86 | for (int i = 0; i < mHeaderDatas.size(); i++) {
87 | SparseArrayCompat sparse = mHeaderDatas.valueAt(i);
88 | if (layoutId == sparse.keyAt(0)) {
89 | sparse.setValueAt(0, data);
90 | isFinded = true;
91 | }
92 | }
93 | if (!isFinded) {//没发现 说明是addHeaderView
94 | addHeaderView(layoutId, data);
95 | }
96 | }
97 |
98 |
99 | /**
100 | * 设置某个位置的HeaderView
101 | *
102 | * @param headerPos 从0开始,如果pos过大 就是addHeaderview
103 | * @param layoutId
104 | * @param data
105 | */
106 | public void setHeaderView(int headerPos, int layoutId, Object data) {
107 | if (mHeaderDatas.size() > headerPos) {
108 | SparseArrayCompat headerContainer = new SparseArrayCompat();
109 | headerContainer.put(layoutId, data);
110 | mHeaderDatas.setValueAt(headerPos, headerContainer);
111 | } else if (mHeaderDatas.size() == headerPos) {//调用addHeaderView
112 | addHeaderView(layoutId, data);
113 | } else {
114 | //
115 | addHeaderView(layoutId, data);
116 | }
117 | }
118 |
119 | /**
120 | * 添加FooterView
121 | *
122 | * @param v
123 | */
124 | public void addFooterView(View v) {
125 | mFooterViews.put(mFooterViews.size() + BASE_ITEM_TYPE_FOOTER, v);
126 | }
127 |
128 | /**
129 | * 清空HeaderView数据
130 | */
131 | public void clearHeaderView() {
132 | mHeaderDatas.clear();
133 | }
134 |
135 | public void clearFooterView() {
136 | mFooterViews.clear();
137 | }
138 |
139 |
140 | public void setFooterView(View v) {
141 | clearFooterView();
142 | addFooterView(v);
143 | }
144 |
145 | @Override
146 | public int getItemViewType(int position) {
147 | if (isHeaderViewPos(position)) {
148 | return mHeaderDatas.keyAt(position);
149 | } else if (isFooterViewPos(position)) {//举例:header 2, innter 2, 0123都不是,4才是,4-2-2 = 0,ok。
150 | return mFooterViews.keyAt(position - getHeaderViewCount() - getInnerItemCount());
151 | }
152 | return super.getItemViewType(position - getHeaderViewCount());
153 | }
154 |
155 | @Override
156 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
157 |
158 | if (mHeaderDatas.get(viewType) != null) {//不为空,说明是headerview
159 | //return new ViewHolder(parent.getContext(), mHeaderViews.get(viewType));
160 | //return createHeader(parent, mHeaderViews.indexOfKey(viewType)); 第一种方法是让子类实现这个方法 构建ViewHolder
161 | return ViewHolder.get(parent.getContext(), null, parent, mHeaderDatas.get(viewType).keyAt(0), -1);
162 | } else if (mFooterViews.get(viewType) != null) {//不为空,说明是footerview
163 | return new ViewHolder(parent.getContext(), mFooterViews.get(viewType));
164 | }
165 | return mInnerAdapter.onCreateViewHolder(parent, viewType);
166 | }
167 |
168 | //protected abstract RecyclerView.ViewHolder createHeader(ViewGroup parent, int headerPos);
169 |
170 | protected abstract void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, Object o);//多回传一个layoutId出去,用于判断是第几个headerview
171 |
172 | @Override
173 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
174 | if (isHeaderViewPos(position)) {
175 | int layoutId = mHeaderDatas.get(getItemViewType(position)).keyAt(0);
176 | onBindHeaderHolder((ViewHolder) holder, position, layoutId, mHeaderDatas.get(getItemViewType(position)).get(layoutId));
177 | return;
178 | } else if (isFooterViewPos(position)) {
179 | return;
180 | }
181 | //举例子,2个header,0 1是头,2是开始,2-2 = 0
182 | mInnerAdapter.onBindViewHolder(holder, position - getHeaderViewCount());
183 | }
184 |
185 |
186 | @Override
187 | public int getItemCount() {
188 | return getInnerItemCount() + getHeaderViewCount() + getFooterViewCount();
189 | }
190 |
191 | @Override
192 | public void onAttachedToRecyclerView(RecyclerView recyclerView) {
193 | mInnerAdapter.onAttachedToRecyclerView(recyclerView);
194 | //为了兼容GridLayout
195 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
196 | if (layoutManager instanceof GridLayoutManager) {
197 | final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
198 | final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
199 |
200 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
201 | @Override
202 | public int getSpanSize(int position) {
203 | int viewType = getItemViewType(position);
204 | if (mHeaderDatas.get(viewType) != null) {
205 | return gridLayoutManager.getSpanCount();
206 | } else if (mFooterViews.get(viewType) != null) {
207 | return gridLayoutManager.getSpanCount();
208 | }
209 | if (spanSizeLookup != null)
210 | return spanSizeLookup.getSpanSize(position);
211 | return 1;
212 | }
213 | });
214 | gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
215 | }
216 |
217 | }
218 |
219 | @Override
220 | public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
221 | mInnerAdapter.onViewAttachedToWindow(holder);
222 | int position = holder.getLayoutPosition();
223 | if (isHeaderViewPos(position) || isFooterViewPos(position)) {
224 | ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
225 |
226 | if (lp != null
227 | && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
228 |
229 | StaggeredGridLayoutManager.LayoutParams p =
230 | (StaggeredGridLayoutManager.LayoutParams) lp;
231 |
232 | p.setFullSpan(true);
233 | }
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/utils/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.utils;
2 |
3 | import android.view.View;
4 | import android.view.ViewGroup;
5 |
6 | /**
7 | * 通用的RecyclerView 的ItemClickListener,包含点击和长按
8 | * Created by zhangxutong .
9 | * Date: 16/03/11
10 | */
11 | public interface OnItemClickListener
12 | {
13 | void onItemClick(ViewGroup parent, View view, T t, int position);
14 | boolean onItemLongClick(ViewGroup parent, View view, T t, int position);
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/mcxtzhang/itemdecorationdemo/utils/ViewHolder.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Paint;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.os.Build;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.text.util.Linkify;
12 | import android.util.SparseArray;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.view.animation.AlphaAnimation;
17 | import android.widget.AbsListView;
18 | import android.widget.Checkable;
19 | import android.widget.ImageView;
20 | import android.widget.ProgressBar;
21 | import android.widget.RatingBar;
22 | import android.widget.TextView;
23 |
24 | /**
25 | * 通用的RecyclerView 的ViewHolder ,使用者无需关心
26 | * Created by zhangxutong .
27 | * Date: 16/03/11
28 | */
29 | public class ViewHolder extends RecyclerView.ViewHolder {
30 | private SparseArray mViews;
31 | private int mPosition;
32 | private View mConvertView;
33 | private Context mContext;
34 | private int mLayoutId;
35 |
36 | public ViewHolder(Context context, View itemView, ViewGroup parent, int position) {
37 | super(itemView);
38 | mContext = context;
39 | mConvertView = itemView;
40 | mPosition = position;
41 | mViews = new SparseArray();
42 | mConvertView.setTag(this);
43 |
44 | }
45 |
46 | public ViewHolder(Context context, View itemView) {
47 | super(itemView);
48 | mContext = context;
49 | mConvertView = itemView;
50 | mViews = new SparseArray();
51 | }
52 |
53 |
54 | public static ViewHolder get(Context context, View convertView,
55 | ViewGroup parent, int layoutId, int position) {
56 | if (convertView == null) {
57 | View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
58 | false);
59 | ViewHolder holder = new ViewHolder(context, itemView, parent, position);
60 | holder.mLayoutId = layoutId;
61 | return holder;
62 | } else {
63 | ViewHolder holder = (ViewHolder) convertView.getTag();
64 | holder.mPosition = position;
65 | return holder;
66 | }
67 | }
68 |
69 |
70 | /**
71 | * 通过viewId获取控件
72 | *
73 | * @param viewId
74 | * @return
75 | */
76 | public T getView(int viewId) {
77 | View view = mViews.get(viewId);
78 | if (view == null) {
79 | view = mConvertView.findViewById(viewId);
80 | mViews.put(viewId, view);
81 | }
82 | return (T) view;
83 | }
84 |
85 | public View getConvertView() {
86 | return mConvertView;
87 | }
88 |
89 |
90 | /**
91 | * 设置TextView的值
92 | *
93 | * @param viewId
94 | * @param text
95 | * @return
96 | */
97 | public ViewHolder setText(int viewId, String text) {
98 | TextView tv = getView(viewId);
99 | tv.setText(text);
100 | return this;
101 | }
102 |
103 | public ViewHolder setSelected(int viewId, boolean selected) {
104 | View v = getView(viewId);
105 | v.setSelected(selected);
106 | return this;
107 | }
108 |
109 | public ViewHolder setImageResource(int viewId, int resId) {
110 | ImageView view = getView(viewId);
111 | view.setImageResource(resId);
112 | return this;
113 | }
114 |
115 | public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
116 | ImageView view = getView(viewId);
117 | view.setImageBitmap(bitmap);
118 | return this;
119 | }
120 |
121 | public ViewHolder setImageDrawable(int viewId, Drawable drawable) {
122 | ImageView view = getView(viewId);
123 | view.setImageDrawable(drawable);
124 | return this;
125 | }
126 |
127 | public ViewHolder setBackgroundColor(int viewId, int color) {
128 | View view = getView(viewId);
129 | view.setBackgroundColor(color);
130 | return this;
131 | }
132 |
133 | public ViewHolder setBackgroundRes(int viewId, int backgroundRes) {
134 | View view = getView(viewId);
135 | view.setBackgroundResource(backgroundRes);
136 | return this;
137 | }
138 |
139 | public ViewHolder setTextColor(int viewId, int textColor) {
140 | TextView view = getView(viewId);
141 | view.setTextColor(textColor);
142 | return this;
143 | }
144 |
145 | public ViewHolder setTextColorRes(int viewId, int textColorRes) {
146 | TextView view = getView(viewId);
147 | view.setTextColor(mContext.getResources().getColor(textColorRes));
148 | return this;
149 | }
150 |
151 | @SuppressLint("NewApi")
152 | public ViewHolder setAlpha(int viewId, float value) {
153 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
154 | getView(viewId).setAlpha(value);
155 | } else {
156 | // Pre-honeycomb hack to set Alpha value
157 | AlphaAnimation alpha = new AlphaAnimation(value, value);
158 | alpha.setDuration(0);
159 | alpha.setFillAfter(true);
160 | getView(viewId).startAnimation(alpha);
161 | }
162 | return this;
163 | }
164 |
165 | public ViewHolder setVisible(int viewId, boolean visible) {
166 | View view = getView(viewId);
167 | view.setVisibility(visible ? View.VISIBLE : View.GONE);
168 | return this;
169 | }
170 |
171 | public ViewHolder linkify(int viewId) {
172 | TextView view = getView(viewId);
173 | Linkify.addLinks(view, Linkify.ALL);
174 | return this;
175 | }
176 |
177 | public ViewHolder setTypeface(Typeface typeface, int... viewIds) {
178 | for (int viewId : viewIds) {
179 | TextView view = getView(viewId);
180 | view.setTypeface(typeface);
181 | view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
182 | }
183 | return this;
184 | }
185 |
186 | public ViewHolder setProgress(int viewId, int progress) {
187 | ProgressBar view = getView(viewId);
188 | view.setProgress(progress);
189 | return this;
190 | }
191 |
192 | public ViewHolder setProgress(int viewId, int progress, int max) {
193 | ProgressBar view = getView(viewId);
194 | view.setMax(max);
195 | view.setProgress(progress);
196 | return this;
197 | }
198 |
199 | public ViewHolder setMax(int viewId, int max) {
200 | ProgressBar view = getView(viewId);
201 | view.setMax(max);
202 | return this;
203 | }
204 |
205 | public ViewHolder setRating(int viewId, float rating) {
206 | RatingBar view = getView(viewId);
207 | view.setRating(rating);
208 | return this;
209 | }
210 |
211 | public ViewHolder setRating(int viewId, float rating, int max) {
212 | RatingBar view = getView(viewId);
213 | view.setMax(max);
214 | view.setRating(rating);
215 | return this;
216 | }
217 |
218 | public ViewHolder setTag(int viewId, Object tag) {
219 | View view = getView(viewId);
220 | view.setTag(tag);
221 | return this;
222 | }
223 |
224 | public ViewHolder setTag(int viewId, int key, Object tag) {
225 | View view = getView(viewId);
226 | view.setTag(key, tag);
227 | return this;
228 | }
229 |
230 | public ViewHolder setChecked(int viewId, boolean checked) {
231 | Checkable view = (Checkable) getView(viewId);
232 | view.setChecked(checked);
233 | return this;
234 | }
235 |
236 | /**
237 | * 关于事件的
238 | */
239 | public ViewHolder setOnClickListener(int viewId,
240 | View.OnClickListener listener) {
241 | View view = getView(viewId);
242 | view.setOnClickListener(listener);
243 | return this;
244 | }
245 |
246 | public ViewHolder setOnTouchListener(int viewId,
247 | View.OnTouchListener listener) {
248 | View view = getView(viewId);
249 | view.setOnTouchListener(listener);
250 | return this;
251 | }
252 |
253 | public ViewHolder setOnLongClickListener(int viewId,
254 | View.OnLongClickListener listener) {
255 | View view = getView(viewId);
256 | view.setOnLongClickListener(listener);
257 | return this;
258 | }
259 |
260 | public void updatePosition(int position) {
261 | mPosition = position;
262 | }
263 |
264 | public int getLayoutId() {
265 | return mLayoutId;
266 | }
267 |
268 | /**
269 | * 隐藏或展示Item
270 | *
271 | * @param visible
272 | */
273 | public void setItemVisible(boolean visible) {
274 | View v = getConvertView();
275 | if (null != v) {
276 | if (visible) {
277 | if (null != v.getLayoutParams()) {
278 | v.getLayoutParams().width = AbsListView.LayoutParams.MATCH_PARENT;
279 | v.getLayoutParams().height = AbsListView.LayoutParams.WRAP_CONTENT;
280 | } else {
281 | v.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT));
282 | }
283 | v.setVisibility(View.VISIBLE);
284 | } else {
285 | if (null != v.getLayoutParams()) {
286 | v.getLayoutParams().width = -1;
287 | v.getLayoutParams().height = 1;
288 | } else {
289 | v.setLayoutParams(new AbsListView.LayoutParams(-1, 1));
290 | }
291 | v.setVisibility(View.GONE);
292 | }
293 | }
294 | }
295 |
296 | public void setHItemVisible(boolean visible) {
297 | View v = getConvertView();
298 | if (null != v) {
299 | if (visible) {
300 | if (null != v.getLayoutParams()) {
301 | v.getLayoutParams().width = AbsListView.LayoutParams.WRAP_CONTENT;
302 | v.getLayoutParams().height = AbsListView.LayoutParams.WRAP_CONTENT;
303 | } else {
304 | v.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT));
305 | }
306 | v.setVisibility(View.VISIBLE);
307 | } else {
308 | if (null != v.getLayoutParams()) {
309 | v.getLayoutParams().width = -1;
310 | v.getLayoutParams().height = 1;
311 | } else {
312 | v.setLayoutParams(new AbsListView.LayoutParams(-1, 1));
313 | }
314 | v.setVisibility(View.GONE);
315 | }
316 | }
317 | }
318 |
319 | }
320 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/friend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/app/src/main/res/drawable-xxhdpi/friend.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/app/src/main/res/drawable-xxhdpi/group.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/meituan_iten_header_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_side_bar_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
20 |
21 |
22 |
27 |
28 |
29 |
34 |
35 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
16 |
23 |
24 |
36 |
37 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_meituan.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
16 |
24 |
25 |
37 |
38 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/header_complex.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_city.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_city_swipe.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
24 |
25 |
32 |
33 |
34 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/meituan_item_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/meituan_item_header_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/meituan_item_header_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/meituan_item_select_city.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 广东
6 | - 北京
7 | - 福建
8 | - 浙江
9 | - 山东
10 | - 江苏
11 | - 河南
12 | - 安徽
13 | - 123
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 | - A
39 | - a
40 | - b
41 | - d
42 |
43 | - .a
44 | - ;b
45 | - #c
46 | - d
47 |
48 | - ab
49 | - aa
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #39000000
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ItemDecorationDemo
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/mcxtzhang/itemdecorationdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package mcxtzhang.itemdecorationdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | maven { url "https://jitpack.io" }
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gif/ItemDecorationIndexBar_SwipeDel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/gif/ItemDecorationIndexBar_SwipeDel.gif
--------------------------------------------------------------------------------
/gif/citylist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/gif/citylist
--------------------------------------------------------------------------------
/gif/meituan.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/gif/meituan.gif
--------------------------------------------------------------------------------
/gif/weixin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/gif/weixin.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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcxtzhang/SuspensionIndexBar/599c709a02f8562e07c64791a0be9c3354fe83a4/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 06 18:56:40 CST 2016
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 |
--------------------------------------------------------------------------------
/indexlib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/indexlib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 24
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(include: ['*.jar'], dir: 'libs')
26 | compile 'com.android.support:appcompat-v7:24.2.1'
27 | compile 'com.android.support:recyclerview-v7:24.2.1'
28 | compile 'com.github.promeg:tinypinyin:1.0.0' // ~80KB
29 | }
--------------------------------------------------------------------------------
/indexlib/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 C:\Users\admin\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/indexlib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/IndexBar/bean/BaseIndexBean.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.IndexBar.bean;
2 |
3 | import com.mcxtzhang.indexlib.suspension.ISuspensionInterface;
4 |
5 | /**
6 | * 介绍:索引类的标志位的实体基类
7 | * 作者:zhangxutong
8 | * 邮箱:mcxtzhang@163.com
9 | * CSDN:http://blog.csdn.net/zxt0601
10 | * 时间: 16/09/04.
11 | */
12 |
13 | public abstract class BaseIndexBean implements ISuspensionInterface {
14 | private String baseIndexTag;//所属的分类(城市的汉语拼音首字母)
15 |
16 | public String getBaseIndexTag() {
17 | return baseIndexTag;
18 | }
19 |
20 | public BaseIndexBean setBaseIndexTag(String baseIndexTag) {
21 | this.baseIndexTag = baseIndexTag;
22 | return this;
23 | }
24 |
25 | @Override
26 | public String getSuspensionTag() {
27 | return baseIndexTag;
28 | }
29 |
30 | @Override
31 | public boolean isShowSuspension() {
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/IndexBar/bean/BaseIndexPinyinBean.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.IndexBar.bean;
2 |
3 | /**
4 | * 介绍:索引类的汉语拼音的接口
5 | * 作者:zhangxutong
6 | * 邮箱:mcxtzhang@163.com
7 | * CSDN:http://blog.csdn.net/zxt0601
8 | * 时间: 16/09/04.
9 | */
10 |
11 | public abstract class BaseIndexPinyinBean extends BaseIndexBean {
12 | private String baseIndexPinyin;//城市的拼音
13 |
14 | public String getBaseIndexPinyin() {
15 | return baseIndexPinyin;
16 | }
17 |
18 | public BaseIndexPinyinBean setBaseIndexPinyin(String baseIndexPinyin) {
19 | this.baseIndexPinyin = baseIndexPinyin;
20 | return this;
21 | }
22 |
23 | //是否需要被转化成拼音, 类似微信头部那种就不需要 美团的也不需要
24 | //微信的头部 不需要显示索引
25 | //美团的头部 索引自定义
26 | //默认应该是需要的
27 | public boolean isNeedToPinyin() {
28 | return true;
29 | }
30 |
31 | //需要转化成拼音的目标字段
32 | public abstract String getTarget();
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/IndexBar/helper/IIndexBarDataHelper.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.IndexBar.helper;
2 |
3 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * 介绍:IndexBar 的 数据相关帮助类
9 | * 1 将汉语转成拼音
10 | * 2 填充indexTag
11 | * 3 排序源数据源
12 | * 4 根据排序后的源数据源->indexBar的数据源
13 | * 作者:zhangxutong
14 | * 邮箱:mcxtzhang@163.com
15 | * 主页:http://blog.csdn.net/zxt0601
16 | * 时间: 2016/11/28.
17 | */
18 |
19 | public interface IIndexBarDataHelper {
20 | //汉语-》拼音
21 | IIndexBarDataHelper convert(List extends BaseIndexPinyinBean> data);
22 |
23 | //拼音->tag
24 | IIndexBarDataHelper fillInexTag(List extends BaseIndexPinyinBean> data);
25 |
26 | //对源数据进行排序(RecyclerView)
27 | IIndexBarDataHelper sortSourceDatas(List extends BaseIndexPinyinBean> datas);
28 |
29 | //对IndexBar的数据源进行排序(右侧栏),在 sortSourceDatas 方法后调用
30 | IIndexBarDataHelper getSortedIndexDatas(List extends BaseIndexPinyinBean> sourceDatas, List datas);
31 | }
32 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/IndexBar/helper/IndexBarDataHelperImpl.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.IndexBar.helper;
2 |
3 | import com.github.promeg.pinyinhelper.Pinyin;
4 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
5 |
6 | import java.util.Collections;
7 | import java.util.Comparator;
8 | import java.util.List;
9 |
10 | /**
11 | * 介绍:IndexBar 的 数据相关帮助类 实现
12 | * * 1 将汉语转成拼音(利用tinyPinyin)
13 | * 2 填充indexTag (取拼音首字母)
14 | * 3 排序源数据源
15 | * 4 根据排序后的源数据源->indexBar的数据源
16 | * 作者:zhangxutong
17 | * 邮箱:mcxtzhang@163.com
18 | * 主页:http://blog.csdn.net/zxt0601
19 | * 时间: 2016/11/28.
20 | */
21 |
22 | public class IndexBarDataHelperImpl implements IIndexBarDataHelper {
23 | /**
24 | * 如果需要,
25 | * 字符->拼音,
26 | *
27 | * @param datas
28 | */
29 | @Override
30 | public IIndexBarDataHelper convert(List extends BaseIndexPinyinBean> datas) {
31 | if (null == datas || datas.isEmpty()) {
32 | return this;
33 | }
34 | int size = datas.size();
35 | for (int i = 0; i < size; i++) {
36 | BaseIndexPinyinBean indexPinyinBean = datas.get(i);
37 | StringBuilder pySb = new StringBuilder();
38 | //add by zhangxutong 2016 11 10 如果不是top 才转拼音,否则不用转了
39 | if (indexPinyinBean.isNeedToPinyin()) {
40 | String target = indexPinyinBean.getTarget();//取出需要被拼音化的字段
41 | //遍历target的每个char得到它的全拼音
42 | for (int i1 = 0; i1 < target.length(); i1++) {
43 | //利用TinyPinyin将char转成拼音
44 | //查看源码,方法内 如果char为汉字,则返回大写拼音
45 | //如果c不是汉字,则返回String.valueOf(c)
46 | pySb.append(Pinyin.toPinyin(target.charAt(i1)).toUpperCase());
47 | }
48 | indexPinyinBean.setBaseIndexPinyin(pySb.toString());//设置城市名全拼音
49 | } else {
50 | //pySb.append(indexPinyinBean.getBaseIndexPinyin());
51 | }
52 | }
53 | return this;
54 | }
55 |
56 | /**
57 | * 如果需要取出,则
58 | * 取出首字母->tag,或者特殊字母 "#".
59 | * 否则,用户已经实现设置好
60 | *
61 | * @param datas
62 | */
63 | @Override
64 | public IIndexBarDataHelper fillInexTag(List extends BaseIndexPinyinBean> datas) {
65 | if (null == datas || datas.isEmpty()) {
66 | return this;
67 | }
68 | int size = datas.size();
69 | for (int i = 0; i < size; i++) {
70 | BaseIndexPinyinBean indexPinyinBean = datas.get(i);
71 | if (indexPinyinBean.isNeedToPinyin()) {
72 | //以下代码设置城市拼音首字母
73 | String tagString = indexPinyinBean.getBaseIndexPinyin().toString().substring(0, 1);
74 | if (tagString.matches("[A-Z]")) {//如果是A-Z字母开头
75 | indexPinyinBean.setBaseIndexTag(tagString);
76 | } else {//特殊字母这里统一用#处理
77 | indexPinyinBean.setBaseIndexTag("#");
78 | }
79 | }
80 | }
81 | return this;
82 | }
83 |
84 | @Override
85 | public IIndexBarDataHelper sortSourceDatas(List extends BaseIndexPinyinBean> datas) {
86 | if (null == datas || datas.isEmpty()) {
87 | return this;
88 | }
89 | convert(datas);
90 | fillInexTag(datas);
91 | //对数据源进行排序
92 | Collections.sort(datas, new Comparator() {
93 | @Override
94 | public int compare(BaseIndexPinyinBean lhs, BaseIndexPinyinBean rhs) {
95 | if (!lhs.isNeedToPinyin()) {
96 | return 0;
97 | } else if (!rhs.isNeedToPinyin()) {
98 | return 0;
99 | } else if (lhs.getBaseIndexTag().equals("#")) {
100 | return 1;
101 | } else if (rhs.getBaseIndexTag().equals("#")) {
102 | return -1;
103 | } else {
104 | return lhs.getBaseIndexPinyin().compareTo(rhs.getBaseIndexPinyin());
105 | }
106 | }
107 | });
108 | return this;
109 | }
110 |
111 | @Override
112 | public IIndexBarDataHelper getSortedIndexDatas(List extends BaseIndexPinyinBean> sourceDatas, List indexDatas) {
113 | if (null == sourceDatas || sourceDatas.isEmpty()) {
114 | return this;
115 | }
116 | //按数据源来 此时sourceDatas 已经有序
117 | int size = sourceDatas.size();
118 | String baseIndexTag;
119 | for (int i = 0; i < size; i++) {
120 | baseIndexTag = sourceDatas.get(i).getBaseIndexTag();
121 | if (!indexDatas.contains(baseIndexTag)) {//则判断是否已经将这个索引添加进去,若没有则添加
122 | indexDatas.add(baseIndexTag);
123 | }
124 | }
125 | return this;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/IndexBar/widget/IndexBar.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.IndexBar.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.support.v7.widget.LinearLayoutManager;
10 | import android.text.TextUtils;
11 | import android.util.AttributeSet;
12 | import android.util.TypedValue;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 | import android.widget.TextView;
16 |
17 | import com.mcxtzhang.indexlib.IndexBar.bean.BaseIndexPinyinBean;
18 | import com.mcxtzhang.indexlib.IndexBar.helper.IIndexBarDataHelper;
19 | import com.mcxtzhang.indexlib.IndexBar.helper.IndexBarDataHelperImpl;
20 | import com.mcxtzhang.indexlib.R;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.List;
25 |
26 |
27 | /**
28 | * 介绍:索引右侧边栏
29 | * 作者:zhangxutong
30 | * 邮箱:mcxtzhang@163.com
31 | * CSDN:http://blog.csdn.net/zxt0601
32 | * 时间: 16/09/04.
33 | */
34 |
35 | public class IndexBar extends View {
36 | private static final String TAG = "zxt/IndexBar";
37 |
38 | //#在最后面(默认的数据源)
39 | public static String[] INDEX_STRING = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
40 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
41 | "W", "X", "Y", "Z", "#"};
42 | //是否需要根据实际的数据来生成索引数据源(例如 只有 A B C 三种tag,那么索引栏就 A B C 三项)
43 | private boolean isNeedRealIndex;
44 | //索引数据源
45 | private List mIndexDatas;
46 |
47 |
48 | //View的宽高
49 | private int mWidth, mHeight;
50 | //每个index区域的高度
51 | private int mGapHeight;
52 |
53 | private Paint mPaint;
54 |
55 | //手指按下时的背景色
56 | private int mPressedBackground;
57 |
58 | //以下是帮助类
59 | //汉语->拼音,拼音->tag
60 | private IIndexBarDataHelper mDataHelper;
61 |
62 | //以下边变量是外部set进来的
63 | private TextView mPressedShowTextView;//用于特写显示正在被触摸的index值
64 | private boolean isSourceDatasAlreadySorted;//源数据 已经有序?
65 | private List extends BaseIndexPinyinBean> mSourceDatas;//Adapter的数据源
66 | private LinearLayoutManager mLayoutManager;
67 | private int mHeaderViewCount = 0;
68 |
69 | public IndexBar(Context context) {
70 | this(context, null);
71 | }
72 |
73 | public IndexBar(Context context, AttributeSet attrs) {
74 | this(context, attrs, 0);
75 | }
76 |
77 | public IndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
78 | super(context, attrs, defStyleAttr);
79 | init(context, attrs, defStyleAttr);
80 | }
81 |
82 | public int getHeaderViewCount() {
83 | return mHeaderViewCount;
84 | }
85 |
86 | /**
87 | * 设置Headerview的Count
88 | *
89 | * @param headerViewCount
90 | * @return
91 | */
92 | public IndexBar setHeaderViewCount(int headerViewCount) {
93 | mHeaderViewCount = headerViewCount;
94 | return this;
95 | }
96 |
97 | public boolean isSourceDatasAlreadySorted() {
98 | return isSourceDatasAlreadySorted;
99 | }
100 |
101 | /**
102 | * 源数据 是否已经有序
103 | *
104 | * @param sourceDatasAlreadySorted
105 | * @return
106 | */
107 | public IndexBar setSourceDatasAlreadySorted(boolean sourceDatasAlreadySorted) {
108 | isSourceDatasAlreadySorted = sourceDatasAlreadySorted;
109 | return this;
110 | }
111 |
112 | public IIndexBarDataHelper getDataHelper() {
113 | return mDataHelper;
114 | }
115 |
116 | /**
117 | * 设置数据源帮助类
118 | *
119 | * @param dataHelper
120 | * @return
121 | */
122 | public IndexBar setDataHelper(IIndexBarDataHelper dataHelper) {
123 | mDataHelper = dataHelper;
124 | return this;
125 | }
126 |
127 | private void init(Context context, AttributeSet attrs, int defStyleAttr) {
128 | int textSize = (int) TypedValue.applyDimension(
129 | TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics());//默认的TextSize
130 | mPressedBackground = Color.BLACK;//默认按下是纯黑色
131 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.IndexBar, defStyleAttr, 0);
132 | int n = typedArray.getIndexCount();
133 | for (int i = 0; i < n; i++) {
134 | int attr = typedArray.getIndex(i);
135 | //modify 2016 09 07 :如果引用成AndroidLib 资源都不是常量,无法使用switch case
136 | if (attr == R.styleable.IndexBar_indexBarTextSize) {
137 | textSize = typedArray.getDimensionPixelSize(attr, textSize);
138 | } else if (attr == R.styleable.IndexBar_indexBarPressBackground) {
139 | mPressedBackground = typedArray.getColor(attr, mPressedBackground);
140 | }
141 | }
142 | typedArray.recycle();
143 |
144 | initIndexDatas();
145 |
146 |
147 | mPaint = new Paint();
148 | mPaint.setAntiAlias(true);
149 | mPaint.setTextSize(textSize);
150 | mPaint.setColor(Color.BLACK);
151 |
152 | //设置index触摸监听器
153 | setmOnIndexPressedListener(new onIndexPressedListener() {
154 | @Override
155 | public void onIndexPressed(int index, String text) {
156 | if (mPressedShowTextView != null) { //显示hintTexView
157 | mPressedShowTextView.setVisibility(View.VISIBLE);
158 | mPressedShowTextView.setText(text);
159 | }
160 | //滑动Rv
161 | if (mLayoutManager != null) {
162 | int position = getPosByTag(text);
163 | if (position != -1) {
164 | mLayoutManager.scrollToPositionWithOffset(position, 0);
165 | }
166 | }
167 | }
168 |
169 | @Override
170 | public void onMotionEventEnd() {
171 | //隐藏hintTextView
172 | if (mPressedShowTextView != null) {
173 | mPressedShowTextView.setVisibility(View.GONE);
174 | }
175 | }
176 | });
177 |
178 | mDataHelper = new IndexBarDataHelperImpl();
179 | }
180 |
181 | @Override
182 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
183 | //取出宽高的MeasureSpec Mode 和Size
184 | int wMode = MeasureSpec.getMode(widthMeasureSpec);
185 | int wSize = MeasureSpec.getSize(widthMeasureSpec);
186 | int hMode = MeasureSpec.getMode(heightMeasureSpec);
187 | int hSize = MeasureSpec.getSize(heightMeasureSpec);
188 | int measureWidth = 0, measureHeight = 0;//最终测量出来的宽高
189 |
190 | //得到合适宽度:
191 | Rect indexBounds = new Rect();//存放每个绘制的index的Rect区域
192 | String index;//每个要绘制的index内容
193 | for (int i = 0; i < mIndexDatas.size(); i++) {
194 | index = mIndexDatas.get(i);
195 | mPaint.getTextBounds(index, 0, index.length(), indexBounds);//测量计算文字所在矩形,可以得到宽高
196 | measureWidth = Math.max(indexBounds.width(), measureWidth);//循环结束后,得到index的最大宽度
197 | measureHeight = Math.max(indexBounds.height(), measureHeight);//循环结束后,得到index的最大高度,然后*size
198 | }
199 | measureHeight *= mIndexDatas.size();
200 | switch (wMode) {
201 | case MeasureSpec.EXACTLY:
202 | measureWidth = wSize;
203 | break;
204 | case MeasureSpec.AT_MOST:
205 | measureWidth = Math.min(measureWidth, wSize);//wSize此时是父控件能给子View分配的最大空间
206 | break;
207 | case MeasureSpec.UNSPECIFIED:
208 | break;
209 | }
210 |
211 | //得到合适的高度:
212 | switch (hMode) {
213 | case MeasureSpec.EXACTLY:
214 | measureHeight = hSize;
215 | break;
216 | case MeasureSpec.AT_MOST:
217 | measureHeight = Math.min(measureHeight, hSize);//wSize此时是父控件能给子View分配的最大空间
218 | break;
219 | case MeasureSpec.UNSPECIFIED:
220 | break;
221 | }
222 |
223 | setMeasuredDimension(measureWidth, measureHeight);
224 | }
225 |
226 |
227 | @Override
228 | protected void onDraw(Canvas canvas) {
229 | int t = getPaddingTop();//top的基准点(支持padding)
230 | String index;//每个要绘制的index内容
231 | for (int i = 0; i < mIndexDatas.size(); i++) {
232 | index = mIndexDatas.get(i);
233 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获得画笔的FontMetrics,用来计算baseLine。因为drawText的y坐标,代表的是绘制的文字的baseLine的位置
234 | int baseline = (int) ((mGapHeight - fontMetrics.bottom - fontMetrics.top) / 2);//计算出在每格index区域,竖直居中的baseLine值
235 | canvas.drawText(index, mWidth / 2 - mPaint.measureText(index) / 2, t + mGapHeight * i + baseline, mPaint);//调用drawText,居中显示绘制index
236 | }
237 | }
238 |
239 |
240 | @Override
241 | public boolean onTouchEvent(MotionEvent event) {
242 | switch (event.getAction()) {
243 | case MotionEvent.ACTION_DOWN:
244 | setBackgroundColor(mPressedBackground);//手指按下时背景变色
245 | //注意这里没有break,因为down时,也要计算落点 回调监听器
246 | case MotionEvent.ACTION_MOVE:
247 | float y = event.getY();
248 | //通过计算判断落点在哪个区域:
249 | int pressI = (int) ((y - getPaddingTop()) / mGapHeight);
250 | //边界处理(在手指move时,有可能已经移出边界,防止越界)
251 | if (pressI < 0) {
252 | pressI = 0;
253 | } else if (pressI >= mIndexDatas.size()) {
254 | pressI = mIndexDatas.size() - 1;
255 | }
256 | //回调监听器
257 | if (null != mOnIndexPressedListener && pressI > -1 && pressI < mIndexDatas.size()) {
258 | mOnIndexPressedListener.onIndexPressed(pressI, mIndexDatas.get(pressI));
259 | }
260 | break;
261 | case MotionEvent.ACTION_UP:
262 | case MotionEvent.ACTION_CANCEL:
263 | default:
264 | setBackgroundResource(android.R.color.transparent);//手指抬起时背景恢复透明
265 | //回调监听器
266 | if (null != mOnIndexPressedListener) {
267 | mOnIndexPressedListener.onMotionEventEnd();
268 | }
269 | break;
270 | }
271 | return true;
272 | }
273 |
274 | @Override
275 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
276 | super.onSizeChanged(w, h, oldw, oldh);
277 | mWidth = w;
278 | mHeight = h;
279 | //add by zhangxutong 2016 09 08 :解决源数据为空 或者size为0的情况,
280 | if (null == mIndexDatas || mIndexDatas.isEmpty()) {
281 | return;
282 | }
283 | computeGapHeight();
284 | }
285 |
286 | /**
287 | * 当前被按下的index的监听器
288 | */
289 | public interface onIndexPressedListener {
290 | void onIndexPressed(int index, String text);//当某个Index被按下
291 |
292 | void onMotionEventEnd();//当触摸事件结束(UP CANCEL)
293 | }
294 |
295 | private onIndexPressedListener mOnIndexPressedListener;
296 |
297 | public onIndexPressedListener getmOnIndexPressedListener() {
298 | return mOnIndexPressedListener;
299 | }
300 |
301 | public void setmOnIndexPressedListener(onIndexPressedListener mOnIndexPressedListener) {
302 | this.mOnIndexPressedListener = mOnIndexPressedListener;
303 | }
304 |
305 | /**
306 | * 显示当前被按下的index的TextView
307 | *
308 | * @return
309 | */
310 |
311 | public IndexBar setmPressedShowTextView(TextView mPressedShowTextView) {
312 | this.mPressedShowTextView = mPressedShowTextView;
313 | return this;
314 | }
315 |
316 | public IndexBar setmLayoutManager(LinearLayoutManager mLayoutManager) {
317 | this.mLayoutManager = mLayoutManager;
318 | return this;
319 | }
320 |
321 | /**
322 | * 一定要在设置数据源{@link #setmSourceDatas(List)}之前调用
323 | *
324 | * @param needRealIndex
325 | * @return
326 | */
327 | public IndexBar setNeedRealIndex(boolean needRealIndex) {
328 | isNeedRealIndex = needRealIndex;
329 | initIndexDatas();
330 | return this;
331 | }
332 |
333 | private void initIndexDatas() {
334 | if (isNeedRealIndex) {
335 | mIndexDatas = new ArrayList<>();
336 | } else {
337 | mIndexDatas = Arrays.asList(INDEX_STRING);
338 | }
339 | }
340 |
341 | public IndexBar setmSourceDatas(List extends BaseIndexPinyinBean> mSourceDatas) {
342 | this.mSourceDatas = mSourceDatas;
343 | initSourceDatas();//对数据源进行初始化
344 | return this;
345 | }
346 |
347 |
348 | /**
349 | * 初始化原始数据源,并取出索引数据源
350 | *
351 | * @return
352 | */
353 | private void initSourceDatas() {
354 | //add by zhangxutong 2016 09 08 :解决源数据为空 或者size为0的情况,
355 | if (null == mSourceDatas || mSourceDatas.isEmpty()) {
356 | return;
357 | }
358 | if (!isSourceDatasAlreadySorted) {
359 | //排序sourceDatas
360 | mDataHelper.sortSourceDatas(mSourceDatas);
361 | } else {
362 | //汉语->拼音
363 | mDataHelper.convert(mSourceDatas);
364 | //拼音->tag
365 | mDataHelper.fillInexTag(mSourceDatas);
366 | }
367 | if (isNeedRealIndex) {
368 | mDataHelper.getSortedIndexDatas(mSourceDatas, mIndexDatas);
369 | computeGapHeight();
370 | }
371 | //sortData();
372 | }
373 |
374 | /**
375 | * 以下情况调用:
376 | * 1 在数据源改变
377 | * 2 控件size改变时
378 | * 计算gapHeight
379 | */
380 | private void computeGapHeight() {
381 | mGapHeight = (mHeight - getPaddingTop() - getPaddingBottom()) / mIndexDatas.size();
382 | }
383 |
384 | /**
385 | * 对数据源排序
386 | */
387 | private void sortData() {
388 |
389 | }
390 |
391 |
392 | /**
393 | * 根据传入的pos返回tag
394 | *
395 | * @param tag
396 | * @return
397 | */
398 | private int getPosByTag(String tag) {
399 | //add by zhangxutong 2016 09 08 :解决源数据为空 或者size为0的情况,
400 | if (null == mSourceDatas || mSourceDatas.isEmpty()) {
401 | return -1;
402 | }
403 | if (TextUtils.isEmpty(tag)) {
404 | return -1;
405 | }
406 | for (int i = 0; i < mSourceDatas.size(); i++) {
407 | if (tag.equals(mSourceDatas.get(i).getBaseIndexTag())) {
408 | return i + getHeaderViewCount();
409 | }
410 | }
411 | return -1;
412 | }
413 |
414 | }
415 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/suspension/ISuspensionInterface.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.suspension;
2 |
3 | /**
4 | * 介绍:分类悬停的接口
5 | * 作者:zhangxutong
6 | * 邮箱:mcxtzhang@163.com
7 | * 主页:http://blog.csdn.net/zxt0601
8 | * 时间: 2016/11/7.
9 | */
10 |
11 | public interface ISuspensionInterface {
12 | //是否需要显示悬停title
13 | boolean isShowSuspension();
14 |
15 | //悬停的title
16 | String getSuspensionTag();
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/indexlib/src/main/java/com/mcxtzhang/indexlib/suspension/SuspensionDecoration.java:
--------------------------------------------------------------------------------
1 | package com.mcxtzhang.indexlib.suspension;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.util.Log;
11 | import android.util.TypedValue;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 |
15 | import java.util.List;
16 |
17 | /**
18 | * 介绍:分类、悬停的Decoration
19 | * 作者:zhangxutong
20 | * 邮箱:mcxtzhang@163.com
21 | * 主页:http://blog.csdn.net/zxt0601
22 | * 时间: 2016/11/7.
23 | */
24 |
25 | public class SuspensionDecoration extends RecyclerView.ItemDecoration {
26 | private List extends ISuspensionInterface> mDatas;
27 | private Paint mPaint;
28 | private Rect mBounds;//用于存放测量文字Rect
29 |
30 | private LayoutInflater mInflater;
31 |
32 | private int mTitleHeight;//title的高
33 | private static int COLOR_TITLE_BG = Color.parseColor("#FFDFDFDF");
34 | private static int COLOR_TITLE_FONT = Color.parseColor("#FF999999");
35 | private static int mTitleFontSize;//title字体大小
36 |
37 | private int mHeaderViewCount = 0;
38 |
39 |
40 | public SuspensionDecoration(Context context, List extends ISuspensionInterface> datas) {
41 | super();
42 | mDatas = datas;
43 | mPaint = new Paint();
44 | mBounds = new Rect();
45 | mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
46 | mTitleFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
47 | mPaint.setTextSize(mTitleFontSize);
48 | mPaint.setAntiAlias(true);
49 | mInflater = LayoutInflater.from(context);
50 | }
51 |
52 |
53 | public SuspensionDecoration setmTitleHeight(int mTitleHeight) {
54 | this.mTitleHeight = mTitleHeight;
55 | return this;
56 | }
57 |
58 |
59 | public SuspensionDecoration setColorTitleBg(int colorTitleBg) {
60 | COLOR_TITLE_BG = colorTitleBg;
61 | return this;
62 | }
63 |
64 | public SuspensionDecoration setColorTitleFont(int colorTitleFont) {
65 | COLOR_TITLE_FONT = colorTitleFont;
66 | return this;
67 | }
68 |
69 | public SuspensionDecoration setTitleFontSize(int mTitleFontSize) {
70 | mPaint.setTextSize(mTitleFontSize);
71 | return this;
72 | }
73 |
74 | public SuspensionDecoration setmDatas(List extends ISuspensionInterface> mDatas) {
75 | this.mDatas = mDatas;
76 | return this;
77 | }
78 |
79 | public int getHeaderViewCount() {
80 | return mHeaderViewCount;
81 | }
82 |
83 | public SuspensionDecoration setHeaderViewCount(int headerViewCount) {
84 | mHeaderViewCount = headerViewCount;
85 | return this;
86 | }
87 |
88 | @Override
89 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
90 | super.onDraw(c, parent, state);
91 | final int left = parent.getPaddingLeft();
92 | final int right = parent.getWidth() - parent.getPaddingRight();
93 | final int childCount = parent.getChildCount();
94 | for (int i = 0; i < childCount; i++) {
95 | final View child = parent.getChildAt(i);
96 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
97 | .getLayoutParams();
98 | int position = params.getViewLayoutPosition();
99 | position -= getHeaderViewCount();
100 | //pos为1,size为1,1>0? true
101 | if (mDatas == null || mDatas.isEmpty() || position > mDatas.size() - 1 || position < 0 || !mDatas.get(position).isShowSuspension()) {
102 | continue;//越界
103 | }
104 | //我记得Rv的item position在重置时可能为-1.保险点判断一下吧
105 | if (position > -1) {
106 | if (position == 0) {//等于0肯定要有title的
107 | drawTitleArea(c, left, right, child, params, position);
108 |
109 | } else {//其他的通过判断
110 | if (null != mDatas.get(position).getSuspensionTag() && !mDatas.get(position).getSuspensionTag().equals(mDatas.get(position - 1).getSuspensionTag())) {
111 | //不为空 且跟前一个tag不一样了,说明是新的分类,也要title
112 | drawTitleArea(c, left, right, child, params, position);
113 | } else {
114 | //none
115 | }
116 | }
117 | }
118 | }
119 | }
120 |
121 | /**
122 | * 绘制Title区域背景和文字的方法
123 | *
124 | * @param c
125 | * @param left
126 | * @param right
127 | * @param child
128 | * @param params
129 | * @param position
130 | */
131 | private void drawTitleArea(Canvas c, int left, int right, View child, RecyclerView.LayoutParams params, int position) {//最先调用,绘制在最下层
132 | mPaint.setColor(COLOR_TITLE_BG);
133 | c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint);
134 | mPaint.setColor(COLOR_TITLE_FONT);
135 | /*
136 | Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
137 | int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;*/
138 |
139 | mPaint.getTextBounds(mDatas.get(position).getSuspensionTag(), 0, mDatas.get(position).getSuspensionTag().length(), mBounds);
140 | c.drawText(mDatas.get(position).getSuspensionTag(), child.getPaddingLeft(), child.getTop() - params.topMargin - (mTitleHeight / 2 - mBounds.height() / 2), mPaint);
141 | }
142 |
143 | @Override
144 | public void onDrawOver(Canvas c, final RecyclerView parent, RecyclerView.State state) {//最后调用 绘制在最上层
145 | int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
146 | pos -= getHeaderViewCount();
147 | //pos为1,size为1,1>0? true
148 | if (mDatas == null || mDatas.isEmpty() || pos > mDatas.size() - 1 || pos < 0 || !mDatas.get(pos).isShowSuspension()) {
149 | return;//越界
150 | }
151 |
152 | String tag = mDatas.get(pos).getSuspensionTag();
153 | //View child = parent.getChildAt(pos);
154 | View child = parent.findViewHolderForLayoutPosition(pos + getHeaderViewCount()).itemView;//出现一个奇怪的bug,有时候child为空,所以将 child = parent.getChildAt(i)。-》 parent.findViewHolderForLayoutPosition(pos).itemView
155 |
156 | boolean flag = false;//定义一个flag,Canvas是否位移过的标志
157 | if ((pos + 1) < mDatas.size()) {//防止数组越界(一般情况不会出现)
158 | if (null != tag && !tag.equals(mDatas.get(pos + 1).getSuspensionTag())) {//当前第一个可见的Item的tag,不等于其后一个item的tag,说明悬浮的View要切换了
159 | Log.d("zxt", "onDrawOver() called with: c = [" + child.getTop());//当getTop开始变负,它的绝对值,是第一个可见的Item移出屏幕的距离,
160 | if (child.getHeight() + child.getTop() < mTitleHeight) {//当第一个可见的item在屏幕中还剩的高度小于title区域的高度时,我们也该开始做悬浮Title的“交换动画”
161 | c.save();//每次绘制前 保存当前Canvas状态,
162 | flag = true;
163 |
164 | //一种头部折叠起来的视效,个人觉得也还不错~
165 | //可与123行 c.drawRect 比较,只有bottom参数不一样,由于 child.getHeight() + child.getTop() < mTitleHeight,所以绘制区域是在不断的减小,有种折叠起来的感觉
166 | //c.clipRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + child.getHeight() + child.getTop());
167 |
168 | //类似饿了么点餐时,商品列表的悬停头部切换“动画效果”
169 | //上滑时,将canvas上移 (y为负数) ,所以后面canvas 画出来的Rect和Text都上移了,有种切换的“动画”感觉
170 | c.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
171 | }
172 | }
173 | }
174 | mPaint.setColor(COLOR_TITLE_BG);
175 | c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint);
176 | mPaint.setColor(COLOR_TITLE_FONT);
177 | mPaint.getTextBounds(tag, 0, tag.length(), mBounds);
178 | c.drawText(tag, child.getPaddingLeft(),
179 | parent.getPaddingTop() + mTitleHeight - (mTitleHeight / 2 - mBounds.height() / 2),
180 | mPaint);
181 | if (flag)
182 | c.restore();//恢复画布到之前保存的状态
183 |
184 |
185 | /* Button button = new Button(parent.getContext());
186 | button.setOnClickListener(new View.OnClickListener() {
187 | @Override
188 | public void onClick(View v) {
189 | Toast.makeText(parent.getContext(), "啊哈", Toast.LENGTH_SHORT).show();//即使给View设置了点击事件,也是无效的,它仅仅draw了
190 | }
191 | });
192 | ViewGroup.LayoutParams params = button.getLayoutParams();
193 | if (params == null){
194 | params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
195 | }
196 | button.setLayoutParams(params);
197 | button.setBackgroundColor(Color.RED);
198 | button.setText("无哈");
199 | *//*button.measure(View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.EXACTLY),View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.EXACTLY));
200 | *//*
201 | //必须经过 测量 和 布局,View才能被正常的显示出来
202 | button.measure(View.MeasureSpec.makeMeasureSpec(9999,View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(9999,View.MeasureSpec.UNSPECIFIED));
203 | button.layout(parent.getPaddingLeft(),parent.getPaddingTop(),
204 | parent.getPaddingLeft()+button.getMeasuredWidth(),parent.getPaddingTop()+button.getMeasuredHeight());
205 | button.draw(c);*/
206 |
207 | //inflate一个复杂布局 并draw出来
208 | /* View toDrawView = mInflater.inflate(R.layout.header_complex, parent, false);
209 | int toDrawWidthSpec;//用于测量的widthMeasureSpec
210 | int toDrawHeightSpec;//用于测量的heightMeasureSpec
211 | //拿到复杂布局的LayoutParams,如果为空,就new一个。
212 | // 后面需要根据这个lp 构建toDrawWidthSpec,toDrawHeightSpec
213 | ViewGroup.LayoutParams lp = toDrawView.getLayoutParams();
214 | if (lp == null) {
215 | lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//这里是根据复杂布局layout的width height,new一个Lp
216 | toDrawView.setLayoutParams(lp);
217 | }
218 | if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
219 | //如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec。
220 | toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY);
221 | } else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
222 | //如果是WRAP_CONTENT,则用父控件能分配的最大宽度和AT_MOST构建MeasureSpec。
223 | toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
224 | } else {
225 | //否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec。
226 | toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
227 | }
228 | //高度同理
229 | if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
230 | toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
231 | } else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
232 | toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
233 | } else {
234 | toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
235 | }
236 | //依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
237 | toDrawView.measure(toDrawWidthSpec, toDrawHeightSpec);
238 | toDrawView.layout(parent.getPaddingLeft(), parent.getPaddingTop(),
239 | parent.getPaddingLeft() + toDrawView.getMeasuredWidth(), parent.getPaddingTop() + toDrawView.getMeasuredHeight());
240 | toDrawView.draw(c);*/
241 |
242 | }
243 |
244 | @Override
245 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
246 | //super里会先设置0 0 0 0
247 | super.getItemOffsets(outRect, view, parent, state);
248 | int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
249 | position -= getHeaderViewCount();
250 | if (mDatas == null || mDatas.isEmpty() || position > mDatas.size() - 1) {//pos为1,size为1,1>0? true
251 | return;//越界
252 | }
253 | //我记得Rv的item position在重置时可能为-1.保险点判断一下吧
254 | if (position > -1) {
255 | ISuspensionInterface titleCategoryInterface = mDatas.get(position);
256 | //等于0肯定要有title的,
257 | // 2016 11 07 add 考虑到headerView 等于0 也不应该有title
258 | // 2016 11 10 add 通过接口里的isShowSuspension() 方法,先过滤掉不想显示悬停的item
259 | if (titleCategoryInterface.isShowSuspension()) {
260 | if (position == 0) {
261 | outRect.set(0, mTitleHeight, 0, 0);
262 | } else {//其他的通过判断
263 | if (null != titleCategoryInterface.getSuspensionTag() && !titleCategoryInterface.getSuspensionTag().equals(mDatas.get(position - 1).getSuspensionTag())) {
264 | //不为空 且跟前一个tag不一样了,说明是新的分类,也要title
265 | outRect.set(0, mTitleHeight, 0, 0);
266 | }
267 | }
268 | }
269 | }
270 | }
271 |
272 | }
273 |
--------------------------------------------------------------------------------
/indexlib/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':indexlib'
2 |
--------------------------------------------------------------------------------