├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── modules.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tuacy │ │ └── recyclerpinnedheader │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tuacy │ │ │ └── recyclerpinnedheader │ │ │ ├── MainActivity.java │ │ │ ├── grid │ │ │ ├── GridRecyclerActivity.java │ │ │ └── GridRecyclerAdapter.java │ │ │ └── linear │ │ │ ├── LinearRecyclerActivity.java │ │ │ └── LinearRecyclerAdapter.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── circle_blue.xml │ │ ├── circle_green.xml │ │ ├── circle_green_dark.xml │ │ ├── circle_orange.xml │ │ ├── circle_purple.xml │ │ ├── circle_red.xml │ │ ├── circle_yellow.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_grid_recycler.xml │ │ ├── activity_linear_recycler.xml │ │ ├── activity_main.xml │ │ ├── item_grid_content.xml │ │ ├── item_grid_title.xml │ │ ├── item_linear_content.xml │ │ └── item_linear_title.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── tuacy │ └── recyclerpinnedheader │ └── ExampleUnitTest.java ├── build.gradle ├── pinnedheader ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tuacy │ │ └── pinnedheader │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tuacy │ │ │ └── pinnedheader │ │ │ ├── IPinnedHeaderDecoration.java │ │ │ ├── PinnedHeaderAdapter.java │ │ │ ├── PinnedHeaderItemDecoration.java │ │ │ └── PinnedHeaderRecyclerView.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── tuacy │ └── pinnedheader │ └── ExampleUnitTest.java ├── recyclerexpand ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tuacy │ │ └── recyclerexpand │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tuacy │ │ │ └── recyclerexpand │ │ │ ├── MainActivity.java │ │ │ ├── PatrolGroupAdapter.java │ │ │ ├── PatrolItem.java │ │ │ ├── expand │ │ │ ├── ExpandGroupIndexEntity.java │ │ │ ├── ExpandGroupItemEntity.java │ │ │ └── RecyclerExpandBaseAdapter.java │ │ │ └── utils │ │ │ ├── DensityUtils.java │ │ │ └── ResourceUtils.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── bg_common.png │ │ ├── ic_down_indicate.png │ │ ├── ic_drag_user.png │ │ ├── ic_message.png │ │ └── ic_up_indicate.png │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── shape_ring_state.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── item_expand_order_sub.xml │ │ └── item_expand_order_title.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── tuacy │ └── recyclerexpand │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | build 6 | gradle.properties 7 | gradlew 8 | gradlew.bat 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |        列表展示是开发过程中经常用到的功能,通常通过 ListView 或者 RecyclerView 控件来实现。在列表显示的过程中可能会碰到这样的需求:需要对列表进行分组,每个分组都有标题 item view 和内容 item view 而且希望列表在滑动的过程中每个分组的标题 item view 可以一直固定的列表的顶部。之前的博客我们已经通过ListView实现了这一功能,有兴趣的可以参考链接[Android分组悬浮列表实现](http://blog.csdn.net/wuyuxing24/article/details/70477566)。but这一次我们通过 RecyclerView 来实现这一需求。实现过程比 ListView 的实现过程要更加简单。 2 | 3 |        在讲怎么实现之前先献上通过RecyclerView实现的效果图 4 | 5 | >LinearLayoutManager实现的效果 6 | 7 | ![LinearLayoutManager实现效果](http://img.blog.csdn.net/20171229093308200?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 8 | 9 | >GridLayoutManager实现的效果,要固定在顶部的 item要占据整个一行 10 | 11 | ![GridLayoutManager实现效果](http://img.blog.csdn.net/20171229094035994?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 12 | 13 |        接下来就是实现过程了 14 | 15 |        我们知道 RecyclerView 给提供了一个RecyclerView.ItemDecoration 来给我们使用,这可是好东西呀。RecyclerView.ItemDecoration 从字面上来看是用来给 RecyclerView 里面每个 item 添加装饰用的(当然也可以给RecyclerView的整体添加装饰)。例如,你可以通过 RecyclerView.ItemDecoration 来给 RecyclerView 的每个 item 添加分割线、给每个 item 添加 padding 等等。这里我们通过 RecyclerView.ItemDecoration 来实现 RecyclerView 分组悬浮列表的功能。 16 | 17 |        我们先简单的看下RecyclerView.ItemDecoration里面几个函数: 18 | 19 | ``` 20 | /** 21 | * 可以通过重写这个函数给RecyclerView绘制任意合适的decorations(装饰) 22 | * 会在RecyclerView item绘制之前绘制。可以认为是绘制在RecyclerView的下面 23 | * 会在RecyclerView类的onDraw()里面调用 24 | */ 25 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 26 | onDraw(c, parent); 27 | } 28 | 29 | /** 30 | * deprecated掉的函数我们不管,忽视掉,不建议使用了 31 | */ 32 | @Deprecated 33 | public void onDraw(Canvas c, RecyclerView parent) { 34 | } 35 | 36 | /** 37 | * 可以通过重写这个函数给RecyclerView绘制任意合适的decorations(装饰) 38 | * 会在RecyclerView item绘制之后绘制。可以认为是绘制在RecyclerView的上面(在上面在盖一层) 39 | * 会在RecyclerView类的super.draw()之后调用, 40 | */ 41 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 42 | onDrawOver(c, parent); 43 | } 44 | 45 | /** 46 | * deprecated掉的函数不建议使用了,忽视掉 47 | */ 48 | @Deprecated 49 | public void onDrawOver(Canvas c, RecyclerView parent) { 50 | } 51 | 52 | 53 | /** 54 | * deprecated掉的函数不建议使用了,忽视掉 55 | */ 56 | @Deprecated 57 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 58 | outRect.set(0, 0, 0, 0); 59 | } 60 | 61 | /** 62 | * 给RecyclerView item对应的每个view增加一些offsets(你可以这么认为item对应的view外面还有一层布局,给这个布局增加padding) 63 | */ 64 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 65 | getItemOffsets(outRect, ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); 66 | } 67 | ``` 68 | >看到 ItemDecoration 提供到我们的就三个函数了:onDraw()、onDrawOver()、getItemOffsets()。getItemOffsets()函数会在 RecyclerView 里面每个子 view 测量的时候调用,可以用来给每个子 view 添加offset(间距)。onDraw()会在RecyclerView的onDraw()方法里面调用。onDrawOver()函数会在RecyclerView的draw()函数里面调用。关于onDraw()、onDrawOver()两个函数的区分咱们可以简单的认为onDraw()是在RecyclerView绘制内容的时候调用。onDrawOver()是在RecyclerView绘制完内容之后再调用,相当于可以在RecyclerView之上在绘制一层内容。 69 | 70 |        通过对 RecyclerView.ItemDecoration 类的简单分析,再结合我们分组固定标题 View 的需求,我们是要把每个分组的标题 View 固定在顶部,恩,那肯定是在要绘制在RecyclerView层之上的吧,和RecyclerView.ItemDecoration里面的onDrawOver()函数正好对应上了。 71 | 72 |        接下来的事情就好办了 73 | 74 |        首先,既然有些标题是要固定的,那咱们一定要明确的知道哪些position位置对应的view是标题吧,只能通过adapter做文章了,所有我们就有了一个基础的PinnedHeaderAdapter,代码如下: 75 | ``` 76 | public abstract class PinnedHeaderAdapter extends RecyclerView.Adapter { 77 | 78 | /** 79 | * 判断该position对应的位置是要固定 80 | * 81 | * @param position adapter position 82 | * @return true or false 83 | */ 84 | public abstract boolean isPinnedPosition(int position); 85 | 86 | } 87 | ``` 88 |        接下来,RecyclerView.ItemDecoration里面的onDrawOver()函数里面我们做好三件事情就好了:第一,找到当前界面要一直固定在顶部的 View、第二,把找到固定在顶部的 View 画在 RecyclerView 的顶部、第三,当将要到达顶部的标题 View 和已经画在顶部的 View 相遇的时候顶部 view 上移的问题。这三个问题实现起来也不复杂,所以这里我们就直接贴代码了,毕竟代码才是王道吗。 89 | 90 | ``` 91 | /** 92 | * 把要固定的View绘制在上层 93 | */ 94 | @Override 95 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 96 | super.onDrawOver(c, parent, state); 97 | //确保是PinnedHeaderAdapter的adapter,确保有View 98 | if (parent.getAdapter() instanceof PinnedHeaderAdapter && parent.getChildCount() > 0) { 99 | PinnedHeaderAdapter adapter = (PinnedHeaderAdapter) parent.getAdapter(); 100 | //找到要固定的pin view 101 | View firstView = parent.getChildAt(0); 102 | int firstAdapterPosition = parent.getChildAdapterPosition(firstView); 103 | int pinnedHeaderPosition = getPinnedHeaderViewPosition(firstAdapterPosition, adapter); 104 | if (pinnedHeaderPosition != -1) { 105 | RecyclerView.ViewHolder pinnedHeaderViewHolder = adapter.onCreateViewHolder(parent, adapter.getItemViewType(pinnedHeaderPosition)); 106 | adapter.onBindViewHolder(pinnedHeaderViewHolder, pinnedHeaderPosition); 107 | //要固定的view 108 | View pinnedHeaderView = pinnedHeaderViewHolder.itemView; 109 | ensurePinnedHeaderViewLayout(pinnedHeaderView, parent); 110 | int sectionPinOffset = 0; 111 | for (int index = 0; index < parent.getChildCount(); index++) { 112 | if (adapter.isPinnedPosition(parent.getChildAdapterPosition(parent.getChildAt(index)))) { 113 | View sectionView = parent.getChildAt(index); 114 | int sectionTop = sectionView.getTop(); 115 | int pinViewHeight = pinnedHeaderView.getHeight(); 116 | if (sectionTop < pinViewHeight && sectionTop > 0) { 117 | sectionPinOffset = sectionTop - pinViewHeight; 118 | } 119 | } 120 | } 121 | int saveCount = c.save(); 122 | c.translate(0, sectionPinOffset); 123 | c.clipRect(0, 0, parent.getWidth(), pinnedHeaderView.getMeasuredHeight()); 124 | pinnedHeaderView.draw(c); 125 | c.restoreToCount(saveCount); 126 | } 127 | 128 | } 129 | } 130 | ``` 131 | 132 |        整个功能到这就结束了,是不是很简单 133 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.tuacy.recyclerpinnedheader" 7 | minSdkVersion 17 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 28 | 29 | implementation project(':pinnedheader') 30 | implementation 'com.android.support:recyclerview-v7:26.1.0' 31 | implementation 'com.android.support:appcompat-v7:26.1.0' 32 | implementation 'com.android.support:cardview-v7:26.1.0' 33 | implementation 'com.squareup.picasso:picasso:2.5.2' 34 | implementation 'de.hdodenhof:circleimageview:2.1.0' 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tuacy/recyclerpinnedheader/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader; 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 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | 20 | @Test 21 | public void useAppContext() throws Exception { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.tuacy.recyclerpinnedheader", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/tuacy/recyclerpinnedheader/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.tuacy.recyclerpinnedheader.grid.GridRecyclerActivity; 9 | import com.tuacy.recyclerpinnedheader.linear.LinearRecyclerActivity; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | 14 | private Context mContext; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | mContext = this; 21 | findViewById(R.id.layout_pinned_header_linear).setOnClickListener(new View.OnClickListener() { 22 | @Override 23 | public void onClick(View v) { 24 | LinearRecyclerActivity.startUp(mContext); 25 | } 26 | }); 27 | findViewById(R.id.layout_pinned_header_grid).setOnClickListener(new View.OnClickListener() { 28 | @Override 29 | public void onClick(View v) { 30 | GridRecyclerActivity.startUp(mContext); 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/tuacy/recyclerpinnedheader/grid/GridRecyclerActivity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader.grid; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.widget.Toast; 10 | 11 | import com.tuacy.pinnedheader.PinnedHeaderItemDecoration; 12 | import com.tuacy.pinnedheader.PinnedHeaderRecyclerView; 13 | import com.tuacy.recyclerpinnedheader.R; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import static android.widget.Toast.LENGTH_SHORT; 19 | 20 | 21 | public class GridRecyclerActivity extends AppCompatActivity { 22 | 23 | public static void startUp(Context context) { 24 | context.startActivity(new Intent(context, GridRecyclerActivity.class)); 25 | } 26 | 27 | private PinnedHeaderRecyclerView mRecyclerView; 28 | private Context mContext; 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_grid_recycler); 34 | mContext = this; 35 | initView(); 36 | initEvent(); 37 | initData(); 38 | } 39 | 40 | private void initView() { 41 | mRecyclerView = findViewById(R.id.recycler_grid); 42 | final GridLayoutManager manager = new GridLayoutManager(mContext, 2); 43 | manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 44 | @Override 45 | public int getSpanSize(int position) { 46 | if (position % 5 == 0) { 47 | return manager.getSpanCount(); 48 | } else { 49 | return 1; 50 | } 51 | } 52 | }); 53 | mRecyclerView.setLayoutManager(manager); 54 | } 55 | 56 | private void initEvent() { 57 | mRecyclerView.setOnPinnedHeaderClickListener(new PinnedHeaderRecyclerView.OnPinnedHeaderClickListener() { 58 | @Override 59 | public void onPinnedHeaderClick(int adapterPosition) { 60 | Toast.makeText(mContext, "点击了悬浮标题 position = " + adapterPosition, LENGTH_SHORT).show(); 61 | } 62 | }); 63 | } 64 | 65 | private void initData() { 66 | GridRecyclerAdapter adapter = new GridRecyclerAdapter(obtainData()); 67 | mRecyclerView.setAdapter(adapter); 68 | mRecyclerView.addItemDecoration(new PinnedHeaderItemDecoration()); 69 | } 70 | 71 | private List obtainData() { 72 | List list = new ArrayList<>(); 73 | list.add("2016-07-20"); 74 | list.add( 75 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 76 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 77 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 78 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 79 | list.add("2016-07-21"); 80 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 81 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 82 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 83 | list.add( 84 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 85 | list.add("2016-07-22"); 86 | list.add( 87 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 88 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 89 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 90 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 91 | list.add("2016-07-23"); 92 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 93 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 94 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 95 | list.add( 96 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 97 | list.add("2016-07-24"); 98 | list.add( 99 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 100 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 101 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 102 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 103 | list.add("2016-07-25"); 104 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 105 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 106 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 107 | list.add( 108 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 109 | list.add("2016-07-26"); 110 | list.add( 111 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 112 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 113 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 114 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 115 | list.add("2016-07-27"); 116 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 117 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 118 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 119 | list.add( 120 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 121 | list.add("2016-07-28"); 122 | list.add( 123 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 124 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 125 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 126 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 127 | list.add("2016-07-29"); 128 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 129 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 130 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 131 | list.add( 132 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 133 | list.add("2016-07-30"); 134 | list.add( 135 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514456311&di=2a8ccd6c814c5851fb8763418dd60455&src=http://wenwen.soso.com/p/20130907/20130907174128-2028703867.jpg"); 136 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1960816299,803825902&fm=27&gp=0.jpg"); 137 | list.add("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3790810349,4012335838&fm=27&gp=0.jpg"); 138 | list.add("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3781123768,373023027&fm=27&gp=0.jpg"); 139 | list.add("2016-07-21"); 140 | list.add("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4052445718,1344904722&fm=27&gp=0.jpg"); 141 | list.add("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1730874133,3861130981&fm=27&gp=0.jpg"); 142 | list.add("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4117837678,3129898700&fm=27&gp=0.jpg"); 143 | list.add( 144 | "https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1514457163&di=b5c6e3c2242452070e8a40a47fca117f&src=http://p4.gexing.com/G1/M00/A2/1B/rBACFFKAfKzzywqAAAAZExvP8P4963_200x200_3.jpg?recache=20131108"); 145 | return list; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/com/tuacy/recyclerpinnedheader/grid/GridRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader.grid; 2 | 3 | 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.TextView; 9 | 10 | import com.squareup.picasso.Picasso; 11 | import com.tuacy.pinnedheader.PinnedHeaderAdapter; 12 | import com.tuacy.recyclerpinnedheader.R; 13 | 14 | import java.util.List; 15 | 16 | import de.hdodenhof.circleimageview.CircleImageView; 17 | 18 | public class GridRecyclerAdapter extends PinnedHeaderAdapter { 19 | 20 | public static final int VIEW_TYPE_ITEM_TIME = 0; 21 | private static final int VIEW_TYPE_ITEM_CONTENT = 1; 22 | 23 | private List mDataList; 24 | 25 | public GridRecyclerAdapter() { 26 | this(null); 27 | } 28 | 29 | public GridRecyclerAdapter(List dataList) { 30 | mDataList = dataList; 31 | } 32 | 33 | @Override 34 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 35 | if (viewType == VIEW_TYPE_ITEM_TIME) { 36 | return new TitleHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_title, parent, false)); 37 | } else { 38 | return new ContentHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_content, parent, false)); 39 | } 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 44 | if (getItemViewType(position) == VIEW_TYPE_ITEM_TIME) { 45 | TitleHolder titleHolder = (TitleHolder) holder; 46 | titleHolder.mTextTitle.setText(mDataList.get(position)); 47 | } else { 48 | ContentHolder contentHolder = (ContentHolder) holder; 49 | Picasso.with(contentHolder.mImage.getContext()).load(mDataList.get(position)).into(contentHolder.mImage); 50 | } 51 | } 52 | 53 | @Override 54 | public int getItemCount() { 55 | return mDataList == null ? 0 : mDataList.size(); 56 | } 57 | 58 | @Override 59 | public int getItemViewType(int position) { 60 | if (position % 5 == 0) { 61 | return VIEW_TYPE_ITEM_TIME; 62 | } else { 63 | return VIEW_TYPE_ITEM_CONTENT; 64 | } 65 | } 66 | 67 | @Override 68 | public boolean isPinnedPosition(int position) { 69 | return getItemViewType(position) == VIEW_TYPE_ITEM_TIME; 70 | } 71 | 72 | static class ContentHolder extends RecyclerView.ViewHolder { 73 | 74 | CircleImageView mImage; 75 | 76 | ContentHolder(View itemView) { 77 | super(itemView); 78 | mImage = itemView.findViewById(R.id.image_icon); 79 | } 80 | } 81 | 82 | static class TitleHolder extends RecyclerView.ViewHolder { 83 | 84 | TextView mTextTitle; 85 | 86 | TitleHolder(View itemView) { 87 | super(itemView); 88 | mTextTitle = itemView.findViewById(R.id.text_adapter_title_name); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/tuacy/recyclerpinnedheader/linear/LinearRecyclerActivity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader.linear; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.widget.Toast; 10 | 11 | import com.tuacy.pinnedheader.PinnedHeaderItemDecoration; 12 | import com.tuacy.pinnedheader.PinnedHeaderRecyclerView; 13 | import com.tuacy.recyclerpinnedheader.R; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import static android.widget.Toast.LENGTH_SHORT; 19 | 20 | 21 | public class LinearRecyclerActivity extends AppCompatActivity { 22 | 23 | public static void startUp(Context context) { 24 | context.startActivity(new Intent(context, LinearRecyclerActivity.class)); 25 | } 26 | 27 | private PinnedHeaderRecyclerView mRecyclerView; 28 | private Context mContext; 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_linear_recycler); 34 | mContext = this; 35 | initView(); 36 | initEvent(); 37 | initData(); 38 | } 39 | 40 | private void initView() { 41 | mRecyclerView = findViewById(R.id.recycler_linear); 42 | LinearLayoutManager layoutManager = new LinearLayoutManager(mContext); 43 | mRecyclerView.setLayoutManager(layoutManager); 44 | } 45 | 46 | private void initEvent() { 47 | mRecyclerView.setOnPinnedHeaderClickListener(new PinnedHeaderRecyclerView.OnPinnedHeaderClickListener() { 48 | @Override 49 | public void onPinnedHeaderClick(int adapterPosition) { 50 | Toast.makeText(mContext, "点击了悬浮标题 position = " + adapterPosition, LENGTH_SHORT).show(); 51 | } 52 | }); 53 | } 54 | 55 | private void initData() { 56 | LinearRecyclerAdapter adapter = new LinearRecyclerAdapter(obtainData()); 57 | mRecyclerView.setAdapter(adapter); 58 | mRecyclerView.addItemDecoration(new PinnedHeaderItemDecoration()); 59 | } 60 | 61 | private List obtainData() { 62 | List list = new ArrayList<>(); 63 | list.add("2016-07-20"); 64 | list.add("萍乡"); 65 | list.add("高安"); 66 | list.add("江西"); 67 | list.add("南昌"); 68 | list.add("2016-07-21"); 69 | list.add("江西"); 70 | list.add("南昌"); 71 | list.add("江西"); 72 | list.add("南昌"); 73 | list.add("2016-07-22"); 74 | list.add("中国"); 75 | list.add("北京"); 76 | list.add("江西"); 77 | list.add("南昌"); 78 | list.add("2016-07-23"); 79 | list.add("辽宁"); 80 | list.add("沈阳"); 81 | list.add("江西"); 82 | list.add("南昌"); 83 | list.add("2016-07-24"); 84 | list.add("辽宁"); 85 | list.add("沈阳"); 86 | list.add("江西"); 87 | list.add("南昌"); 88 | list.add("2016-07-25"); 89 | list.add("辽宁"); 90 | list.add("沈阳"); 91 | list.add("江西"); 92 | list.add("南昌"); 93 | list.add("2016-07-26"); 94 | list.add("辽宁"); 95 | list.add("沈阳"); 96 | list.add("江西"); 97 | list.add("南昌"); 98 | list.add("2016-07-27"); 99 | list.add("辽宁"); 100 | list.add("沈阳"); 101 | list.add("江西"); 102 | list.add("南昌"); 103 | list.add("2016-07-28"); 104 | list.add("辽宁"); 105 | list.add("沈阳"); 106 | list.add("江西"); 107 | list.add("南昌"); 108 | list.add("2016-07-29"); 109 | list.add("辽宁"); 110 | list.add("沈阳"); 111 | list.add("江西"); 112 | list.add("南昌"); 113 | list.add("2016-07-30"); 114 | list.add("辽宁"); 115 | list.add("沈阳"); 116 | list.add("江西"); 117 | list.add("南昌"); 118 | list.add("2016-07-21"); 119 | list.add("辽宁"); 120 | list.add("沈阳"); 121 | list.add("江西"); 122 | list.add("南昌"); 123 | return list; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/tuacy/recyclerpinnedheader/linear/LinearRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader.linear; 2 | 3 | 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.TextView; 9 | 10 | import com.tuacy.pinnedheader.PinnedHeaderAdapter; 11 | import com.tuacy.recyclerpinnedheader.R; 12 | 13 | import java.util.List; 14 | 15 | public class LinearRecyclerAdapter extends PinnedHeaderAdapter { 16 | 17 | private static final int VIEW_TYPE_ITEM_TIME = 0; 18 | private static final int VIEW_TYPE_ITEM_CONTENT = 1; 19 | 20 | private List mDataList; 21 | 22 | public LinearRecyclerAdapter() { 23 | this(null); 24 | } 25 | 26 | public LinearRecyclerAdapter(List dataList) { 27 | mDataList = dataList; 28 | } 29 | 30 | public void setData(List dataList) { 31 | mDataList = dataList; 32 | notifyDataSetChanged(); 33 | } 34 | 35 | @Override 36 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | if (viewType == VIEW_TYPE_ITEM_TIME) { 38 | return new TitleHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_linear_title, parent, false)); 39 | } else { 40 | return new ContentHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_linear_content, parent, false)); 41 | } 42 | } 43 | 44 | @Override 45 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 46 | if (getItemViewType(position) == VIEW_TYPE_ITEM_TIME) { 47 | TitleHolder titleHolder = (TitleHolder) holder; 48 | titleHolder.mTextTitle.setText(mDataList.get(position)); 49 | } else { 50 | ContentHolder contentHolder = (ContentHolder) holder; 51 | contentHolder.mTextTitle.setText(mDataList.get(position)); 52 | } 53 | } 54 | 55 | @Override 56 | public int getItemCount() { 57 | return mDataList == null ? 0 : mDataList.size(); 58 | } 59 | 60 | @Override 61 | public int getItemViewType(int position) { 62 | if (position % 5 == 0) { 63 | return VIEW_TYPE_ITEM_TIME; 64 | } else { 65 | return VIEW_TYPE_ITEM_CONTENT; 66 | } 67 | } 68 | 69 | @Override 70 | public boolean isPinnedPosition(int position) { 71 | return getItemViewType(position) == VIEW_TYPE_ITEM_TIME; 72 | } 73 | 74 | static class ContentHolder extends RecyclerView.ViewHolder { 75 | 76 | TextView mTextTitle; 77 | 78 | ContentHolder(View itemView) { 79 | super(itemView); 80 | mTextTitle = itemView.findViewById(R.id.text_adapter_content_name); 81 | } 82 | } 83 | 84 | static class TitleHolder extends RecyclerView.ViewHolder { 85 | 86 | TextView mTextTitle; 87 | 88 | TitleHolder(View itemView) { 89 | super(itemView); 90 | mTextTitle = itemView.findViewById(R.id.text_adapter_title_name); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_green_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_orange.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_purple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_yellow.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_grid_recycler.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_linear_recycler.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 24 | 25 | 32 | 33 | 34 | 35 | 41 | 42 | 50 | 51 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_grid_content.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_grid_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_linear_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_linear_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #ff4444 8 | #33b5e5 9 | #669900 10 | #ff8800 11 | #b388ff 12 | #ffff8d 13 | #00e676 14 | 15 | #000000 16 | #eeeeee 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RecyclerPinnedHeader 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/tuacy/recyclerpinnedheader/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerpinnedheader; 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 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.2' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /pinnedheader/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /pinnedheader/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:26.1.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 34 | 35 | implementation 'com.android.support:recyclerview-v7:26.1.0' 36 | } 37 | -------------------------------------------------------------------------------- /pinnedheader/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /pinnedheader/src/androidTest/java/com/tuacy/pinnedheader/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 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 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | 20 | @Test 21 | public void useAppContext() throws Exception { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.tuacy.pinnedheader.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pinnedheader/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /pinnedheader/src/main/java/com/tuacy/pinnedheader/IPinnedHeaderDecoration.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 2 | 3 | import android.graphics.Rect; 4 | 5 | public interface IPinnedHeaderDecoration { 6 | 7 | Rect getPinnedHeaderRect(); 8 | 9 | int getPinnedHeaderPosition(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /pinnedheader/src/main/java/com/tuacy/pinnedheader/PinnedHeaderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 2 | 3 | 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | public abstract class PinnedHeaderAdapter extends RecyclerView.Adapter { 8 | 9 | /** 10 | * 判断该position对应的位置是要固定 11 | * 12 | * @param position adapter position 13 | * @return true or false 14 | */ 15 | public abstract boolean isPinnedPosition(int position); 16 | 17 | 18 | public RecyclerView.ViewHolder onCreatePinnedViewHolder(ViewGroup parent, int viewType) { 19 | return onCreateViewHolder(parent, viewType); 20 | } 21 | 22 | public void onBindPinnedViewHolder(VH holder, int position) { 23 | onBindViewHolder(holder, position); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /pinnedheader/src/main/java/com/tuacy/pinnedheader/PinnedHeaderItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * PinnedHeader对应的ItemDecoration 10 | */ 11 | public class PinnedHeaderItemDecoration extends RecyclerView.ItemDecoration implements IPinnedHeaderDecoration { 12 | 13 | private Rect mPinnedHeaderRect = null; 14 | private int mPinnedHeaderPosition = -1; 15 | 16 | /** 17 | * 把要固定的View绘制在上层 18 | */ 19 | @Override 20 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 21 | super.onDrawOver(c, parent, state); 22 | //确保是PinnedHeaderAdapter的adapter,确保有View 23 | if (parent.getAdapter() instanceof PinnedHeaderAdapter && parent.getChildCount() > 0) { 24 | PinnedHeaderAdapter adapter = (PinnedHeaderAdapter) parent.getAdapter(); 25 | //找到要固定的pin view 26 | View firstView = parent.getChildAt(0); 27 | int firstAdapterPosition = parent.getChildAdapterPosition(firstView); 28 | int pinnedHeaderPosition = getPinnedHeaderViewPosition(firstAdapterPosition, adapter); 29 | mPinnedHeaderPosition = pinnedHeaderPosition; 30 | if (pinnedHeaderPosition != -1) { 31 | RecyclerView.ViewHolder pinnedHeaderViewHolder = adapter.onCreateViewHolder(parent, 32 | adapter.getItemViewType(pinnedHeaderPosition)); 33 | adapter.onBindViewHolder(pinnedHeaderViewHolder, pinnedHeaderPosition); 34 | //要固定的view 35 | View pinnedHeaderView = pinnedHeaderViewHolder.itemView; 36 | ensurePinnedHeaderViewLayout(pinnedHeaderView, parent); 37 | int sectionPinOffset = 0; 38 | for (int index = 0; index < parent.getChildCount(); index++) { 39 | if (adapter.isPinnedPosition(parent.getChildAdapterPosition(parent.getChildAt(index)))) { 40 | View sectionView = parent.getChildAt(index); 41 | int sectionTop = sectionView.getTop(); 42 | int pinViewHeight = pinnedHeaderView.getHeight(); 43 | if (sectionTop < pinViewHeight && sectionTop > 0) { 44 | sectionPinOffset = sectionTop - pinViewHeight; 45 | } 46 | } 47 | } 48 | int saveCount = c.save(); 49 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) pinnedHeaderView.getLayoutParams(); 50 | if (layoutParams == null) { 51 | throw new NullPointerException("PinnedHeaderItemDecoration"); 52 | } 53 | c.translate(layoutParams.leftMargin, sectionPinOffset); 54 | c.clipRect(0, 0, parent.getWidth(), pinnedHeaderView.getMeasuredHeight()); 55 | pinnedHeaderView.draw(c); 56 | c.restoreToCount(saveCount); 57 | if (mPinnedHeaderRect == null) { 58 | mPinnedHeaderRect = new Rect(); 59 | } 60 | mPinnedHeaderRect.set(0, 0, parent.getWidth(), pinnedHeaderView.getMeasuredHeight() + sectionPinOffset); 61 | } else { 62 | mPinnedHeaderRect = null; 63 | } 64 | 65 | } 66 | } 67 | 68 | /** 69 | * 要给每个item设置间距主要靠这个函数来实现 70 | */ 71 | @Override 72 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 73 | } 74 | 75 | /** 76 | * 根据第一个可见的adapter的位置去获取临近的一个要固定的position的位置 77 | * 78 | * @param adapterFirstVisible 第一个可见的adapter的位置 79 | * @return -1:未找到 >=0 找到位置 80 | */ 81 | private int getPinnedHeaderViewPosition(int adapterFirstVisible, PinnedHeaderAdapter adapter) { 82 | for (int index = adapterFirstVisible; index >= 0; index--) { 83 | if (adapter.isPinnedPosition(index)) { 84 | return index; 85 | } 86 | } 87 | return -1; 88 | } 89 | 90 | private void ensurePinnedHeaderViewLayout(View pinView, RecyclerView recyclerView) { 91 | if (pinView.isLayoutRequested()) { 92 | /** 93 | * 用的是RecyclerView的宽度测量,和RecyclerView的宽度一样 94 | */ 95 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) pinView.getLayoutParams(); 96 | if (layoutParams == null) { 97 | throw new NullPointerException("PinnedHeaderItemDecoration"); 98 | } 99 | int widthSpec = View.MeasureSpec.makeMeasureSpec( 100 | recyclerView.getMeasuredWidth() - layoutParams.leftMargin - layoutParams.rightMargin, View.MeasureSpec.EXACTLY); 101 | 102 | int heightSpec; 103 | if (layoutParams.height > 0) { 104 | heightSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY); 105 | } else { 106 | heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 107 | } 108 | pinView.measure(widthSpec, heightSpec); 109 | pinView.layout(0, 0, pinView.getMeasuredWidth(), pinView.getMeasuredHeight()); 110 | } 111 | } 112 | 113 | @Override 114 | public Rect getPinnedHeaderRect() { 115 | return mPinnedHeaderRect; 116 | } 117 | 118 | @Override 119 | public int getPinnedHeaderPosition() { 120 | return mPinnedHeaderPosition; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pinnedheader/src/main/java/com/tuacy/pinnedheader/PinnedHeaderRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.AttributeSet; 8 | import android.view.MotionEvent; 9 | 10 | public class PinnedHeaderRecyclerView extends RecyclerView { 11 | 12 | public interface OnPinnedHeaderClickListener { 13 | 14 | void onPinnedHeaderClick(int adapterPosition); 15 | } 16 | 17 | public PinnedHeaderRecyclerView(Context context) { 18 | super(context); 19 | } 20 | 21 | public PinnedHeaderRecyclerView(Context context, @Nullable AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public PinnedHeaderRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | } 28 | 29 | private OnPinnedHeaderClickListener mPinnedHeaderClickListener; 30 | 31 | public void setOnPinnedHeaderClickListener(OnPinnedHeaderClickListener listener) { 32 | mPinnedHeaderClickListener = listener; 33 | } 34 | 35 | private boolean mPinnedHeaderHandle; 36 | 37 | 38 | @Override 39 | public boolean onInterceptTouchEvent(MotionEvent e) { 40 | if (mPinnedHeaderClickListener == null) { 41 | return super.onInterceptTouchEvent(e); 42 | } 43 | IPinnedHeaderDecoration pinnedHeaderInterface = getPinnedHeaderDecoration(); 44 | if (pinnedHeaderInterface == null) { 45 | return super.onInterceptTouchEvent(e); 46 | } 47 | Rect pinnedHeaderRect = pinnedHeaderInterface.getPinnedHeaderRect(); 48 | int pinnedHeaderPosition = pinnedHeaderInterface.getPinnedHeaderPosition(); 49 | if (pinnedHeaderRect == null || pinnedHeaderPosition == -1) { 50 | return super.onInterceptTouchEvent(e); 51 | } 52 | switch (e.getAction()) { 53 | case MotionEvent.ACTION_DOWN: 54 | if (pinnedHeaderRect.contains((int) e.getX(), (int) e.getY())) { 55 | return true; 56 | } 57 | break; 58 | } 59 | return super.onInterceptTouchEvent(e); 60 | } 61 | 62 | 63 | /** 64 | * 如果有固定的header的情况 65 | */ 66 | @Override 67 | public boolean onTouchEvent(MotionEvent ev) { 68 | if (mPinnedHeaderClickListener == null) { 69 | return super.onTouchEvent(ev); 70 | } 71 | IPinnedHeaderDecoration pinnedHeaderInterface = getPinnedHeaderDecoration(); 72 | if (pinnedHeaderInterface == null) { 73 | return super.onTouchEvent(ev); 74 | } 75 | Rect pinnedHeaderRect = pinnedHeaderInterface.getPinnedHeaderRect(); 76 | int pinnedHeaderPosition = pinnedHeaderInterface.getPinnedHeaderPosition(); 77 | if (pinnedHeaderRect == null || pinnedHeaderPosition == -1) { 78 | return super.onTouchEvent(ev); 79 | } 80 | switch (ev.getAction()) { 81 | case MotionEvent.ACTION_DOWN: 82 | mPinnedHeaderHandle = false; 83 | if (pinnedHeaderRect.contains((int) ev.getX(), (int) ev.getY())) { 84 | mPinnedHeaderHandle = true; 85 | return true; 86 | } 87 | break; 88 | case MotionEvent.ACTION_MOVE: 89 | if (mPinnedHeaderHandle) { 90 | if (!pinnedHeaderRect.contains((int) ev.getX(), (int) ev.getY())) { 91 | MotionEvent cancel = MotionEvent.obtain(ev); 92 | cancel.setAction(MotionEvent.ACTION_CANCEL); 93 | super.dispatchTouchEvent(cancel); 94 | 95 | MotionEvent down = MotionEvent.obtain(ev); 96 | down.setAction(MotionEvent.ACTION_DOWN); 97 | return super.dispatchTouchEvent(down); 98 | } else { 99 | return true; 100 | } 101 | } 102 | break; 103 | case MotionEvent.ACTION_CANCEL: 104 | case MotionEvent.ACTION_UP: 105 | float x = ev.getX(); 106 | float y = ev.getY(); 107 | // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发pinned header 点击 108 | if (mPinnedHeaderHandle && pinnedHeaderRect.contains((int) x, (int) y)) { 109 | mPinnedHeaderClickListener.onPinnedHeaderClick(pinnedHeaderPosition); 110 | mPinnedHeaderHandle = false; 111 | return true; 112 | } 113 | mPinnedHeaderHandle = false; 114 | break; 115 | default: 116 | break; 117 | } 118 | return super.onTouchEvent(ev); 119 | } 120 | 121 | public IPinnedHeaderDecoration getPinnedHeaderDecoration() { 122 | int decorationIndex = 0; 123 | ItemDecoration itemDecoration; 124 | do { 125 | itemDecoration = getItemDecorationAt(decorationIndex); 126 | if (itemDecoration instanceof IPinnedHeaderDecoration) { 127 | return (IPinnedHeaderDecoration) itemDecoration; 128 | } 129 | decorationIndex++; 130 | } while (itemDecoration != null); 131 | return null; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /pinnedheader/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PinnedHeader 3 | 4 | -------------------------------------------------------------------------------- /pinnedheader/src/test/java/com/tuacy/pinnedheader/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.pinnedheader; 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 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } -------------------------------------------------------------------------------- /recyclerexpand/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /recyclerexpand/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | 7 | 8 | defaultConfig { 9 | applicationId "com.tuacy.recyclerexpand" 10 | minSdkVersion 17 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | implementation 'com.android.support:appcompat-v7:26.1.0' 32 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 36 | 37 | implementation project(':pinnedheader') 38 | implementation 'com.android.support:recyclerview-v7:26.1.0' 39 | implementation 'com.android.support:appcompat-v7:26.1.0' 40 | implementation 'com.android.support:cardview-v7:26.1.0' 41 | 42 | } 43 | -------------------------------------------------------------------------------- /recyclerexpand/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /recyclerexpand/src/androidTest/java/com/tuacy/recyclerexpand/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand; 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 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.tuacy.recyclerexpand", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand; 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 | 8 | import com.tuacy.pinnedheader.PinnedHeaderItemDecoration; 9 | import com.tuacy.pinnedheader.PinnedHeaderRecyclerView; 10 | import com.tuacy.recyclerexpand.expand.ExpandGroupItemEntity; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private Context mContext; 18 | private PinnedHeaderRecyclerView mRecyclerView; 19 | private LinearLayoutManager mLayoutManager; 20 | private PatrolGroupAdapter mAdapter; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | mContext = this; 27 | initView(); 28 | initEvent(); 29 | initData(); 30 | } 31 | 32 | private void initView() { 33 | mRecyclerView = findViewById(R.id.recycler_order_list); 34 | mRecyclerView.setLayoutManager(mLayoutManager = new LinearLayoutManager(mContext)); 35 | mRecyclerView.addItemDecoration(new PinnedHeaderItemDecoration()); 36 | } 37 | 38 | private void initEvent() { 39 | /** 40 | * 当标题栏被悬浮的时候的点击功能 41 | */ 42 | mRecyclerView.setOnPinnedHeaderClickListener(new PinnedHeaderRecyclerView.OnPinnedHeaderClickListener() { 43 | @Override 44 | public void onPinnedHeaderClick(int adapterPosition) { 45 | mAdapter.switchExpand(adapterPosition); 46 | //标题栏被点击之后,滑动到指定位置 47 | mLayoutManager.scrollToPositionWithOffset(adapterPosition, 0); 48 | } 49 | }); 50 | } 51 | 52 | private void initData() { 53 | mAdapter = new PatrolGroupAdapter(); 54 | mAdapter.setData(obtainDataList()); 55 | mRecyclerView.setAdapter(mAdapter); 56 | } 57 | 58 | private List> obtainDataList() { 59 | List> dataList = new ArrayList<>(); 60 | 61 | for (int group = 0; group < 10; group++) { 62 | ExpandGroupItemEntity groupItem = new ExpandGroupItemEntity<>(); 63 | groupItem.setExpand(true); 64 | groupItem.setParent("分组 " + group); 65 | List childList = new ArrayList<>(); 66 | for (int child = 0; child < group + 1; child++) { 67 | PatrolItem childItem = new PatrolItem(); 68 | childItem.setTime("2018-04-20 15:00"); 69 | childItem.setFactoryName((2000 + child) + " 项目"); 70 | childItem.setUser("电工 " + child); 71 | childItem.setState(child % 5); 72 | childList.add(childItem); 73 | } 74 | groupItem.setChildList(childList); 75 | dataList.add(groupItem); 76 | } 77 | 78 | return dataList; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/PatrolGroupAdapter.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.GradientDrawable; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.tuacy.recyclerexpand.expand.ExpandGroupItemEntity; 13 | import com.tuacy.recyclerexpand.expand.RecyclerExpandBaseAdapter; 14 | import com.tuacy.recyclerexpand.utils.DensityUtils; 15 | import com.tuacy.recyclerexpand.utils.ResourceUtils; 16 | 17 | 18 | public class PatrolGroupAdapter extends RecyclerExpandBaseAdapter { 19 | 20 | 21 | /** 22 | * 悬浮标题栏被点击的时候,展开收起切换功能 23 | */ 24 | public void switchExpand(int adapterPosition) { 25 | int groupIndex = mIndexMap.get(adapterPosition).getGroupIndex(); 26 | ExpandGroupItemEntity entity = mDataList.get(groupIndex); 27 | entity.setExpand(!entity.isExpand()); 28 | notifyDataSetChanged(); 29 | } 30 | 31 | @Override 32 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | if (viewType == VIEW_TYPE_ITEM_TIME) { 34 | TitleItemHolder holder = new TitleItemHolder( 35 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_expand_order_title, parent, false)); 36 | holder.itemView.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | ExpandGroupItemEntity entity = (ExpandGroupItemEntity) v.getTag(); 40 | entity.setExpand(!entity.isExpand()); 41 | notifyDataSetChanged(); 42 | } 43 | }); 44 | return holder; 45 | } else { 46 | return new SubItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_expand_order_sub, parent, false)); 47 | } 48 | } 49 | 50 | @Override 51 | public RecyclerView.ViewHolder onCreatePinnedViewHolder(ViewGroup parent, int viewType) { 52 | TitleItemHolder holder = (TitleItemHolder) super.onCreatePinnedViewHolder(parent, viewType); 53 | holder.mViewSpace.setVisibility(View.GONE); 54 | return holder; 55 | } 56 | 57 | @Override 58 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 59 | if (getItemViewType(position) == VIEW_TYPE_ITEM_TIME) { 60 | int groupIndex = mIndexMap.get(position).getGroupIndex(); 61 | TitleItemHolder itemHolder = (TitleItemHolder) holder; 62 | itemHolder.itemView.setTag(mDataList.get(groupIndex)); 63 | itemHolder.mTextTime.setText(mDataList.get(groupIndex).getParent()); 64 | itemHolder.mImageExpandFlag.setImageResource( 65 | mDataList.get(groupIndex).isExpand() ? R.drawable.ic_up_indicate : R.drawable.ic_down_indicate); 66 | if (mDataList.get(groupIndex).isExpand()) { 67 | if (mIndexMap.get(position).getChildCount() == 0) { 68 | itemHolder.mViewSpace.setVisibility(View.VISIBLE); 69 | } else { 70 | itemHolder.mViewSpace.setVisibility(View.GONE); 71 | } 72 | } else { 73 | itemHolder.mViewSpace.setVisibility(View.VISIBLE); 74 | } 75 | } else { 76 | SubItemHolder subHolder = (SubItemHolder) holder; 77 | int groupIndex = mIndexMap.get(position).getGroupIndex(); 78 | int childIndex = mIndexMap.get(position).getChildIndex(); 79 | PatrolItem subItem = mDataList.get(groupIndex).getChildList().get(childIndex); 80 | subHolder.itemView.setTag(subItem); 81 | subHolder.mTextTime.setText(subItem.getTime().substring(11, 16)); 82 | subHolder.mTextCompanyName.setText(subItem.getFactoryName()); 83 | subHolder.mTextUsers.setText(subItem.getUser()); 84 | subHolder.mTextState.setText(getStateDes(subHolder.mTextState.getContext(), subItem.getState())); 85 | subHolder.mTextState.setTextColor(getStateColor(subHolder.mTextState.getContext(), subItem.getState())); 86 | GradientDrawable gradientDrawable = (GradientDrawable) subHolder.mImageState.getBackground(); 87 | gradientDrawable.setStroke(DensityUtils.dp2px(subHolder.mImageState.getContext(), 2), 88 | getStateColor(subHolder.mImageState.getContext(), subItem.getState())); 89 | } 90 | } 91 | 92 | @Override 93 | public void onBindPinnedViewHolder(RecyclerView.ViewHolder holder, int position) { 94 | super.onBindPinnedViewHolder(holder, position); 95 | TitleItemHolder itemHolder = (TitleItemHolder) holder; 96 | itemHolder.mViewSpace.setVisibility(View.GONE); 97 | } 98 | 99 | private static int getStateColor(Context context, int state) { 100 | int color = ResourceUtils.getColor(context, R.color.order_state_waiting_reception); 101 | switch (state) { 102 | case 0: 103 | color = ResourceUtils.getColor(context, R.color.order_state_waiting_reception); 104 | break; 105 | case 1: 106 | color = ResourceUtils.getColor(context, R.color.order_state_distributed); 107 | break; 108 | case 2: 109 | color = ResourceUtils.getColor(context, R.color.order_state_progressing); 110 | break; 111 | case 3: 112 | color = ResourceUtils.getColor(context, R.color.order_state_auditing); 113 | break; 114 | case 4: 115 | color = ResourceUtils.getColor(context, R.color.order_state_finished); 116 | break; 117 | case 5: 118 | color = ResourceUtils.getColor(context, R.color.order_state_not_started); 119 | break; 120 | } 121 | return color; 122 | } 123 | 124 | private static String getStateDes(Context context, int state) { 125 | String des = context.getString(R.string.waiting_reception); 126 | switch (state) { 127 | case 0: 128 | des = context.getString(R.string.waiting_reception); 129 | break; 130 | case 1: 131 | des = context.getString(R.string.distributed); 132 | break; 133 | case 2: 134 | des = context.getString(R.string.progressing); 135 | break; 136 | case 3: 137 | des = context.getString(R.string.auditing); 138 | break; 139 | case 4: 140 | des = context.getString(R.string.finished); 141 | break; 142 | case 5: 143 | des = context.getString(R.string.not_started); 144 | break; 145 | } 146 | return des; 147 | } 148 | 149 | static class TitleItemHolder extends RecyclerView.ViewHolder { 150 | 151 | View mViewSpace; 152 | TextView mTextTime; 153 | ImageView mImageExpandFlag; 154 | 155 | TitleItemHolder(View itemView) { 156 | super(itemView); 157 | mViewSpace = itemView.findViewById(R.id.view_space); 158 | mTextTime = itemView.findViewById(R.id.text_time); 159 | mImageExpandFlag = itemView.findViewById(R.id.image_expand_flag); 160 | } 161 | } 162 | 163 | static class SubItemHolder extends RecyclerView.ViewHolder { 164 | 165 | ImageView mImageState; 166 | TextView mTextTime; 167 | TextView mTextCompanyName; 168 | TextView mTextUsers; 169 | TextView mTextState; 170 | 171 | SubItemHolder(View itemView) { 172 | super(itemView); 173 | mImageState = itemView.findViewById(R.id.image_state); 174 | mTextTime = itemView.findViewById(R.id.text_time); 175 | mTextCompanyName = itemView.findViewById(R.id.text_company_name); 176 | mTextUsers = itemView.findViewById(R.id.text_repair_user); 177 | mTextState = itemView.findViewById(R.id.text_repair_flag); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/PatrolItem.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand; 2 | 3 | public class PatrolItem { 4 | 5 | private String mTime; 6 | private String mFactoryName; 7 | private String mUser; 8 | private int mState; 9 | 10 | public String getTime() { 11 | return mTime; 12 | } 13 | 14 | public void setTime(String time) { 15 | mTime = time; 16 | } 17 | 18 | public String getFactoryName() { 19 | return mFactoryName; 20 | } 21 | 22 | public void setFactoryName(String factoryName) { 23 | mFactoryName = factoryName; 24 | } 25 | 26 | public String getUser() { 27 | return mUser; 28 | } 29 | 30 | public void setUser(String user) { 31 | mUser = user; 32 | } 33 | 34 | public int getState() { 35 | return mState; 36 | } 37 | 38 | public void setState(int state) { 39 | mState = state; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/expand/ExpandGroupIndexEntity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand.expand; 2 | 3 | /** 4 | * RecyclerView adapter 里面每个position对应的信息 5 | */ 6 | public class ExpandGroupIndexEntity { 7 | 8 | private int mGroupIndex; 9 | private int mChildIndex; 10 | private int mChildCount; 11 | 12 | public ExpandGroupIndexEntity(int groupIndex, int childIndex, int childCount) { 13 | mGroupIndex = groupIndex; 14 | mChildIndex = childIndex; 15 | mChildCount = childCount; 16 | } 17 | 18 | public int getGroupIndex() { 19 | return mGroupIndex; 20 | } 21 | 22 | public void setGroupIndex(int groupIndex) { 23 | mGroupIndex = groupIndex; 24 | } 25 | 26 | public int getChildIndex() { 27 | return mChildIndex; 28 | } 29 | 30 | public void setChildIndex(int childIndex) { 31 | mChildIndex = childIndex; 32 | } 33 | 34 | public int getChildCount() { 35 | return mChildCount; 36 | } 37 | 38 | public void setChildCount(int childCount) { 39 | mChildCount = childCount; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/expand/ExpandGroupItemEntity.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand.expand; 2 | 3 | 4 | import java.util.List; 5 | 6 | /** 7 | * 每个分组对应的entity 8 | * 9 | * @param 标题栏entity 10 | * @param 子项entity 11 | */ 12 | public class ExpandGroupItemEntity { 13 | 14 | /** 15 | * 分组对应的标题栏 16 | */ 17 | private G mParent; 18 | /** 19 | * 分组里面的子项 20 | */ 21 | private List mChildList; 22 | /** 23 | * 分组展开还是收起 24 | */ 25 | private boolean mExpand; 26 | 27 | public G getParent() { 28 | return mParent; 29 | } 30 | 31 | public void setParent(G parent) { 32 | mParent = parent; 33 | } 34 | 35 | public List getChildList() { 36 | return mChildList; 37 | } 38 | 39 | public void setChildList(List childList) { 40 | mChildList = childList; 41 | } 42 | 43 | public boolean isExpand() { 44 | return mExpand; 45 | } 46 | 47 | public void setExpand(boolean expand) { 48 | mExpand = expand; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/expand/RecyclerExpandBaseAdapter.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand.expand; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.SparseArray; 5 | 6 | import com.tuacy.pinnedheader.PinnedHeaderAdapter; 7 | 8 | import java.util.List; 9 | 10 | public abstract class RecyclerExpandBaseAdapter extends PinnedHeaderAdapter { 11 | 12 | protected static final int VIEW_TYPE_ITEM_TIME = 0; 13 | protected static final int VIEW_TYPE_ITEM_CONTENT = 1; 14 | 15 | protected List> mDataList; 16 | protected SparseArray mIndexMap; 17 | 18 | public RecyclerExpandBaseAdapter() { 19 | this(null); 20 | } 21 | 22 | public RecyclerExpandBaseAdapter(List> dataList) { 23 | mDataList = dataList; 24 | mIndexMap = new SparseArray<>(); 25 | } 26 | 27 | public void setData(List> dataList) { 28 | mDataList = dataList; 29 | mIndexMap.clear(); 30 | notifyDataSetChanged(); 31 | } 32 | 33 | public List> getData() { 34 | return mDataList; 35 | } 36 | 37 | @Override 38 | public boolean isPinnedPosition(int position) { 39 | return getItemViewType(position) == VIEW_TYPE_ITEM_TIME; 40 | } 41 | 42 | @Override 43 | public int getItemViewType(int position) { 44 | int count = 0; 45 | for (ExpandGroupItemEntity item : mDataList) { 46 | count = count + 1; 47 | if (position == count - 1) { 48 | return VIEW_TYPE_ITEM_TIME; 49 | } 50 | if (item.getChildList() != null && item.isExpand()) { 51 | count = count + item.getChildList().size(); 52 | } 53 | if (position < count) { 54 | return VIEW_TYPE_ITEM_CONTENT; 55 | } 56 | } 57 | throw new IllegalArgumentException("getItemViewType exception"); 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | if (mDataList == null || mDataList.isEmpty()) { 63 | return 0; 64 | } 65 | int count = 0; 66 | for (int group = 0; group < mDataList.size(); group++) { 67 | ExpandGroupItemEntity item = mDataList.get(group); 68 | //标题 69 | count = count + 1; 70 | 71 | mIndexMap.put(count - 1, new ExpandGroupIndexEntity(group, -1, item.getChildList() == null ? 0 : item.getChildList().size())); 72 | int childStartPosition = count; 73 | if (item.getChildList() != null && item.isExpand()) { 74 | //sub 75 | count = count + item.getChildList().size(); 76 | } 77 | int childEndPosition = count; 78 | for (int loop = childStartPosition; loop < childEndPosition; loop++) { 79 | mIndexMap.put(loop, new ExpandGroupIndexEntity(group, loop - childStartPosition, 80 | item.getChildList() == null ? 0 : item.getChildList().size())); 81 | } 82 | } 83 | return count; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/utils/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand.utils; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | /** 7 | * 常用单位转换的辅助类 8 | */ 9 | public class DensityUtils { 10 | 11 | private DensityUtils() { 12 | throw new UnsupportedOperationException("cannot be instantiated"); 13 | } 14 | 15 | /** 16 | * dp转px 17 | */ 18 | public static int dp2px(Context context, float dpVal) { 19 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); 20 | } 21 | 22 | /** 23 | * sp转px 24 | */ 25 | public static int sp2px(Context context, float spVal) { 26 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources().getDisplayMetrics()); 27 | } 28 | 29 | /** 30 | * px转dp 31 | */ 32 | public static float px2dp(Context context, float pxVal) { 33 | final float scale = context.getResources().getDisplayMetrics().density; 34 | return (pxVal / scale); 35 | } 36 | 37 | /** 38 | * px转sp 39 | */ 40 | public static float px2sp(Context context, float pxVal) { 41 | return (pxVal / context.getResources().getDisplayMetrics().scaledDensity); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/java/com/tuacy/recyclerexpand/utils/ResourceUtils.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand.utils; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.support.annotation.ColorRes; 6 | 7 | public class ResourceUtils { 8 | 9 | public static int getColor(Context context, @ColorRes int colorId) { 10 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { 11 | //noinspection deprecation 12 | return context.getResources().getColor(colorId); 13 | } else { 14 | return context.getColor(colorId); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-xhdpi/bg_common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/drawable-xhdpi/bg_common.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-xhdpi/ic_down_indicate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/drawable-xhdpi/ic_down_indicate.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-xhdpi/ic_drag_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/drawable-xhdpi/ic_drag_user.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-xhdpi/ic_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/drawable-xhdpi/ic_message.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable-xhdpi/ic_up_indicate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/drawable-xhdpi/ic_up_indicate.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/drawable/shape_ring_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/layout/item_expand_order_sub.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 30 | 31 | 37 | 38 | 50 | 51 | 58 | 59 | 69 | 70 | 75 | 76 | 86 | 87 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/layout/item_expand_order_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 23 | 24 | 34 | 35 | 36 | 37 | 43 | 44 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuacy/RecyclerPinnedHeader/02ea221444a6b4810c49c158fb0930d87bd59906/recyclerexpand/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #074C79 4 | #074C79 5 | #33D8FD 6 | 7 | #f7f7f7 8 | #33d8fd 9 | #cccccc 10 | #EBF5FE 11 | #EEEEEE 12 | #0effffff 13 | #62B2B2B2 14 | #EEEEEE 15 | #afaca6 16 | 17 | 18 | #ffb600 19 | 20 | #dbdb00 21 | 22 | #33d142 23 | 24 | #32dfef 25 | 26 | #32dfef 27 | 28 | #ffb600 29 | 30 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | recyclerexpand 3 | RecyclerView收缩分组悬浮 4 | 5 | 待受理 6 | 已派发 7 | 进行中 8 | 审核中 9 | 已完成 10 | 未开始 11 | 12 | -------------------------------------------------------------------------------- /recyclerexpand/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /recyclerexpand/src/test/java/com/tuacy/recyclerexpand/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.tuacy.recyclerexpand; 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 | 14 | @Test 15 | public void addition_isCorrect() { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':pinnedheader', ':recyclerexpand' 2 | --------------------------------------------------------------------------------