├── .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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 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 | 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 | ![image](https://github.com/mcxtzhang/ItemDecorationIndexBar/blob/master/gif/ItemDecorationIndexBar_SwipeDel.gif) 38 | 39 | 高仿美团选择城市界面(MeituanSelectCityActivity): 40 | 41 | ![image](https://github.com/mcxtzhang/ItemDecorationIndexBar/blob/master/gif/meituan.gif) 42 | 43 | 高仿微信通讯录界面(WeChatActivity): 44 | 45 | ![image](https://github.com/mcxtzhang/ItemDecorationIndexBar/blob/master/gif/weixin.gif) 46 | 47 | 普通城市列表界面(MainActivity): 48 | 49 | ![image](https://github.com/mcxtzhang/ItemDecorationIndexBar/blob/master/gif/citylist) 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 |