├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── easyandroid │ │ └── sectionadapter │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── easyandroid │ │ │ └── sectionadapter │ │ │ ├── MainActivity.java │ │ │ ├── TestAdapter.java │ │ │ ├── adapter │ │ │ ├── SectionedRecyclerViewAdapter.java │ │ │ └── SectionedSpanSizeLookup.java │ │ │ ├── entity │ │ │ └── TestEntity.java │ │ │ ├── holder │ │ │ ├── EmptyViewHolder.java │ │ │ ├── FooterHolder.java │ │ │ ├── TestSectionBodyHolder.java │ │ │ ├── TestSectionFooterHolder.java │ │ │ ├── TestSectionHeaderHolder.java │ │ │ └── TypeAbstractViewHolder.java │ │ │ ├── listener │ │ │ ├── LoadMoreListener.java │ │ │ └── RecycleViewScrollHelper.java │ │ │ ├── mvp │ │ │ ├── base │ │ │ │ ├── BasePresenter.java │ │ │ │ ├── BaseView.java │ │ │ │ └── Module.java │ │ │ ├── model │ │ │ │ └── TestModel.java │ │ │ └── presenter │ │ │ │ └── TestPresenter.java │ │ │ ├── util │ │ │ ├── DatasUtil.java │ │ │ └── ListUtil.java │ │ │ └── widgets │ │ │ └── SectionedGridDivider.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── item_section_body.xml │ │ ├── item_section_footer.xml │ │ ├── item_section_header.xml │ │ └── layout_footer.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 │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── easyandroid │ └── sectionadapter │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pictures ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png └── 7.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SectionRecyclerViewAdapter 2 | 3 | 针对各种复杂布局做总结,完整实现典型的场景 4 | 5 | > 1. 实现典型分组复杂列表适配器;
6 | > 2. 实现分组分割线;
7 | > 3. 实现上拉加载的功能,添加回调,添加滑动的容差值;
8 | > 4. 实现列数的控制;
9 | > 5. 支持设置空布局,动态监听数据
10 | > 6. 归纳复杂布局场景,使得通用性更为广泛
11 | 12 | ### 复杂布局样式1: 13 | 14 | ![复杂布局样式1](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/1.png) 15 | 16 | --- 17 | 18 | ### 复杂布局样式2: 19 | 20 | 21 | ![复杂布局样式2](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/2.png) 22 | 23 | --- 24 | 25 | ### 复杂布局样式3: 26 | 27 | 28 | ![复杂布局样式3](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/3.png) 29 | 30 | --- 31 | 32 | ### 复杂布局样式4: 33 | 34 | 35 | ![复杂布局样式4](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/4.png) 36 | 37 | --- 38 | 39 | ### 复杂布局样式5: 40 | 41 | 42 | ![复杂布局样式5](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/5.png) 43 | 44 | --- 45 | 46 | ### 复杂布局样式6: 47 | 48 | 49 | ![复杂布局样式6](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/6.png) 50 | 51 | --- 52 | 53 | ### 典型的样式实现效果: 54 | 55 | 56 | ![典型的样式实现效果](https://github.com/gycold/SectionRecyclerViewAdapter/blob/master/pictures/7.png) 57 | 58 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'android-apt' 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.2" 7 | defaultConfig { 8 | applicationId "com.easyandroid.sectionadapter" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | compile 'com.android.support:appcompat-v7:25.3.1' 29 | compile 'com.android.support.constraint:constraint-layout:1.0.1' 30 | testCompile 'junit:junit:4.12' 31 | compile 'com.easyandroid:easytools:1.1.9' 32 | compile 'com.android.support:recyclerview-v7:25.3.1' 33 | compile 'com.github.bumptech.glide:glide:4.0.0-RC1' 34 | annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1' 35 | compile 'com.jakewharton:butterknife:8.4.0' 36 | apt 'com.jakewharton:butterknife-compiler:8.4.0' 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/easyandroid/sectionadapter/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.easyandroid.sectionadapter", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | 11 | import com.easyandroid.sectionadapter.adapter.SectionedRecyclerViewAdapter; 12 | import com.easyandroid.sectionadapter.adapter.SectionedSpanSizeLookup; 13 | import com.easyandroid.sectionadapter.entity.TestEntity; 14 | import com.easyandroid.sectionadapter.listener.LoadMoreListener; 15 | import com.easyandroid.sectionadapter.mvp.base.Module; 16 | import com.easyandroid.sectionadapter.mvp.presenter.TestPresenter; 17 | import com.easyandroid.sectionadapter.util.ListUtil; 18 | import com.easyandroid.sectionadapter.widgets.SectionedGridDivider; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import butterknife.BindView; 24 | import butterknife.ButterKnife; 25 | 26 | public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout 27 | .OnRefreshListener, Module.View { 28 | 29 | @BindView(R.id.swip_root) 30 | SwipeRefreshLayout refreshLayout; 31 | @BindView(R.id.rv) 32 | RecyclerView rv; 33 | 34 | private TestAdapter mAdapter; 35 | private GridLayoutManager mGridLayoutManager; 36 | private SectionedGridDivider mDivider; 37 | private List mDatas = new ArrayList<>(); 38 | private boolean isPull = true;//是否下拉刷新 39 | 40 | private LoadMoreListener loadMoreListener; 41 | 42 | private TestPresenter mPresenter; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_main); 48 | ButterKnife.bind(this); 49 | mPresenter = new TestPresenter(this); 50 | initAdapter(); 51 | } 52 | 53 | private void initAdapter() { 54 | mAdapter = new TestAdapter(mDatas, this); 55 | mGridLayoutManager = new GridLayoutManager(this, 3); 56 | mGridLayoutManager.setSpanSizeLookup(new SectionedSpanSizeLookup(mAdapter, 57 | mGridLayoutManager)); 58 | rv.setLayoutManager(mGridLayoutManager); 59 | rv.setAdapter(mAdapter); 60 | mDivider = new SectionedGridDivider(this, 50, Color.parseColor("#F5F5F5")); 61 | rv.addItemDecoration(mDivider); 62 | 63 | loadMoreListener = new LoadMoreListener(mGridLayoutManager) { 64 | @Override 65 | public void onLoadMore() { 66 | isPull = false; 67 | isLoading = true; 68 | mAdapter.changeMoreStatus(SectionedRecyclerViewAdapter.LOADING_MORE); 69 | new Handler().postDelayed(new Runnable() { 70 | @Override 71 | public void run() { 72 | mPresenter.loadData(1); 73 | } 74 | }, 1000); 75 | } 76 | }; 77 | rv.setOnScrollListener(loadMoreListener); 78 | 79 | refreshLayout.setOnRefreshListener(this); 80 | 81 | new Handler().postDelayed(new Runnable() { 82 | @Override 83 | public void run() { 84 | mPresenter.loadData(1); 85 | } 86 | }, 300); 87 | } 88 | 89 | @Override 90 | public void onRefresh() { 91 | new Handler().postDelayed(new Runnable() { 92 | @Override 93 | public void run() { 94 | mPresenter.loadData(0); 95 | } 96 | }, 1000); 97 | } 98 | 99 | @Override 100 | public void showLoading(String msg) { 101 | 102 | } 103 | 104 | @Override 105 | public void hideLoading() { 106 | 107 | } 108 | 109 | @Override 110 | public void showError(String errorMsg) { 111 | 112 | } 113 | 114 | @Override 115 | public void updateList(int type, List datas) { 116 | loadMoreListener.isLoading = false; 117 | if (refreshLayout.isRefreshing()) { 118 | refreshLayout.setRefreshing(false); 119 | } 120 | if (isPull) { 121 | if (!ListUtil.isEmpty(datas)) { 122 | mAdapter.getData().clear(); 123 | mAdapter.notifyDataSetChanged(); 124 | mAdapter.addMoreData(datas); 125 | if (loadMoreListener.isFullAScreen(rv)) {//显示item满一屏了 126 | mAdapter.changeMoreStatus(SectionedRecyclerViewAdapter.PULLUP_LOAD_MORE); 127 | } else { 128 | mAdapter.changeMoreStatus(SectionedRecyclerViewAdapter.LOADING_FINISH); 129 | } 130 | mAdapter.notifyDataSetChanged(); 131 | } else { 132 | //显示空布局 133 | } 134 | } else { 135 | if (datas.size() > 0) { 136 | mAdapter.addMoreData(datas); 137 | mAdapter.changeMoreStatus(SectionedRecyclerViewAdapter.PULLUP_LOAD_MORE); 138 | } else { 139 | mAdapter.changeMoreStatus(SectionedRecyclerViewAdapter.LOADING_FINISH); 140 | } 141 | } 142 | 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/TestAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.ViewGroup; 8 | 9 | import com.bumptech.glide.Glide; 10 | import com.easyandroid.sectionadapter.adapter.SectionedRecyclerViewAdapter; 11 | import com.easyandroid.sectionadapter.entity.TestEntity; 12 | import com.easyandroid.sectionadapter.holder.FooterHolder; 13 | import com.easyandroid.sectionadapter.holder.TestSectionBodyHolder; 14 | import com.easyandroid.sectionadapter.holder.TestSectionFooterHolder; 15 | import com.easyandroid.sectionadapter.holder.TestSectionHeaderHolder; 16 | import com.easyandroid.sectionadapter.util.ListUtil; 17 | import com.easytools.tools.DisplayUtil; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * package: com.easyandroid.sectionadapter.TestAdapter 23 | * author: gyc 24 | * description: 25 | * time: create at 2017/7/8 2:59 26 | */ 27 | 28 | public class TestAdapter extends SectionedRecyclerViewAdapter { 31 | 32 | private List mDatas; 33 | private Context mContext; 34 | private LayoutInflater mInflater; 35 | 36 | public TestAdapter(List mDatas, Context mContext) { 37 | this.mDatas = mDatas; 38 | this.mContext = mContext; 39 | mInflater = LayoutInflater.from(mContext); 40 | } 41 | 42 | public void setData(List mDatas) { 43 | this.mDatas = mDatas; 44 | notifyDataSetChanged(); 45 | } 46 | 47 | public List getData() { 48 | return mDatas; 49 | } 50 | 51 | public void addMoreData(List newDatas) { 52 | mDatas.addAll(newDatas); 53 | notifyDataSetChanged(); 54 | } 55 | 56 | @Override 57 | protected boolean hasHeader() { 58 | return false; 59 | } 60 | 61 | @Override 62 | protected int getSectionCount() { 63 | return ListUtil.isEmpty(mDatas) ? 0 : mDatas.size(); 64 | } 65 | 66 | @Override 67 | protected int getItemCountForSection(int section) { 68 | return ListUtil.isEmpty(mDatas.get(section).getEPicture()) ? 0 : mDatas.get(section) 69 | .getEPicture().size(); 70 | 71 | } 72 | 73 | @Override 74 | protected boolean hasFooterInSection(int section) { 75 | return true; 76 | } 77 | 78 | @Override 79 | protected TestSectionHeaderHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int 80 | viewType) { 81 | return new TestSectionHeaderHolder(mInflater.inflate(R.layout 82 | .item_section_header, parent, false)); 83 | } 84 | 85 | @Override 86 | protected TestSectionFooterHolder onCreateSectionFooterViewHolder(ViewGroup parent, int 87 | viewType) { 88 | return new TestSectionFooterHolder(mInflater.inflate(R.layout 89 | .item_section_footer, parent, false)); 90 | 91 | } 92 | 93 | @Override 94 | protected TestSectionBodyHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { 95 | return new TestSectionBodyHolder(mInflater.inflate(R.layout.item_section_body, 96 | parent, false)); 97 | } 98 | 99 | @Override 100 | protected RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType) { 101 | return null; 102 | } 103 | 104 | @Override 105 | protected FooterHolder onCreateFooterViewHolder(ViewGroup parent, int viewType) { 106 | return new FooterHolder(mInflater.inflate(R.layout.layout_footer, parent, false)); 107 | } 108 | 109 | @Override 110 | protected void onBindSectionHeaderViewHolder(TestSectionHeaderHolder holder, int section) { 111 | Glide.with(mContext).load(mDatas.get(section).getPicture()).into(holder.imgHead); 112 | holder.tvNike.setText(mDatas.get(section).getUserName()); 113 | holder.tvDate.setText(mDatas.get(section).getTime()); 114 | holder.tvEvaluate.setText(mDatas.get(section).getContent()); 115 | } 116 | 117 | @Override 118 | protected void onBindItemViewHolder(TestSectionBodyHolder holder, int section, int position) { 119 | int screenWidth = DisplayUtil.getScreenWidthPixels((Activity) mContext); 120 | int imgWidth = (screenWidth - DisplayUtil.dp2px(mContext, 55 + 30)) / 3; 121 | ViewGroup.MarginLayoutParams params = null; 122 | if (holder.llRoot.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 123 | params = (ViewGroup.MarginLayoutParams) holder.llRoot.getLayoutParams(); 124 | } else { 125 | params = new ViewGroup.MarginLayoutParams(holder.llRoot.getLayoutParams()); 126 | } 127 | params.width = imgWidth; 128 | params.height = imgWidth; 129 | 130 | //这里左右边距不相同,左边距与评论文字相同,加上头像的大小,为55dp,左边距为55dp,右边距为10dp,图片间距为10dp 131 | if (position % 3 == 0) { 132 | params.leftMargin = DisplayUtil.dp2px(mContext, 55); 133 | } else if (position % 3 == 1) { 134 | params.leftMargin = DisplayUtil.dp2px(mContext, 35); 135 | } else { 136 | params.leftMargin = DisplayUtil.dp2px(mContext, 14); 137 | } 138 | params.bottomMargin = DisplayUtil.dp2px(mContext, 8); 139 | holder.llRoot.setLayoutParams(params); 140 | Glide.with(mContext).load(mDatas.get(section).getEPicture().get(position)).into(holder 141 | .imgEvaluate); 142 | } 143 | 144 | @Override 145 | protected void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) { 146 | 147 | } 148 | 149 | @Override 150 | protected void onBindSectionFooterViewHolder(TestSectionFooterHolder holder, int section) { 151 | holder.tvLookNum.setText(mContext.getString(R.string.item_section_footer, mDatas.get 152 | (section).getBrowser())); 153 | } 154 | 155 | @Override 156 | protected void onBindFooterOtherViewHolder(FooterHolder holder) { 157 | 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/adapter/SectionedRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import com.easyandroid.sectionadapter.holder.EmptyViewHolder; 8 | import com.easyandroid.sectionadapter.holder.FooterHolder; 9 | 10 | /** 11 | * package: com.easyandroid.sectionadapter.adapter.SectionedRecyclerViewAdapter 12 | * author: gyc 13 | * description:分组适配器基类 14 | * time: create at 2017/7/7 20:12 15 | */ 16 | 17 | public abstract class SectionedRecyclerViewAdapter extends 20 | RecyclerView.Adapter { 21 | 22 | //用来标记每个分组的Header 23 | protected static final int TYPE_SECTION_HEADER = -1; 24 | 25 | //用来标记每个分组的Footer 26 | protected static final int TYPE_SECTION_FOOTER = -2; 27 | 28 | //用来标记每个分组的内容 29 | protected static final int TYPE_ITEM = -3; 30 | 31 | //用来标记整个列表的Header 32 | protected static final int TYPE_HEADER = 0; //顶部HeaderView 33 | 34 | //用来标记整个列表的Footer 35 | protected static final int TYPE_FOOTER = 1; //底部FooterView 36 | 37 | //上拉加载更多 38 | public static final int PULLUP_LOAD_MORE = 0; 39 | 40 | //正在加载中 41 | public static final int LOADING_MORE = 1; 42 | 43 | //加载完成 44 | public static final int LOADING_FINISH = 2; 45 | 46 | public static final int TYPE_EMPTY = -4; 47 | 48 | //上拉加载默认状态--默认为-1 49 | public int load_more_status = -1; 50 | 51 | //用来保存分组section位置 52 | private int[] sectionForPosition = null; 53 | 54 | //用来保存分组内的每项的position位置 55 | private int[] positionWithinSection = null; 56 | 57 | //用来记录每个位置是否是一个组内Header 58 | private boolean[] isHeader = null; 59 | 60 | //用来记录每个位置是否是一个组内Footer 61 | private boolean[] isFooter = null; 62 | 63 | //item的总数,注意,是总数,包含所有项 64 | private int count = 0; 65 | 66 | //以下接口对应各个item的点击事件 67 | public OnChildClickListener onChildClickListener; 68 | public OnItemClickListener onItemClickListener; 69 | public OnItemLongClickListener onItemLongClickListener; 70 | public OnSectionHeaderClickListener onSectionHeaderClickListener; 71 | public OnSectionFooterClickListener onSectionFooterClickListener; 72 | 73 | private View emptyView; 74 | private boolean emptyViewVisible; 75 | 76 | public View getEmptyView() { 77 | return emptyView; 78 | } 79 | 80 | public void setEmptyView(View emptyView) { 81 | this.emptyView = emptyView; 82 | } 83 | 84 | public SectionedRecyclerViewAdapter() { 85 | super(); 86 | //RecyclerView采用观察者(Observer)模式,对外提供了registerDataSetObserver和unregisterDataSetObserver 87 | //两个方法,用来监控数据集的变化 88 | registerAdapterDataObserver(new SectionDataObserver());//主要用于注册与解绑适配器数据的观察者模式 89 | } 90 | 91 | //定义一个内部类,每当数据集合发生改变时,设置控件的位置信息 92 | class SectionDataObserver extends RecyclerView.AdapterDataObserver { 93 | @Override 94 | public void onChanged() { 95 | setupPosition(); 96 | checkEmpty();//检查数据是否为空,设置空布局 97 | } 98 | @Override 99 | public void onItemRangeInserted(int positionStart, int itemCount) { 100 | checkEmpty();//检查数据是否为空,设置空布局 101 | } 102 | 103 | @Override 104 | public void onItemRangeRemoved(int positionStart, int itemCount) { 105 | checkEmpty();//检查数据是否为空,设置空布局 106 | } 107 | } 108 | 109 | private void checkEmpty() { 110 | if (emptyView != null) { 111 | if (hasHeader()) { 112 | emptyViewVisible = getItemCount() == 2; 113 | } else { 114 | emptyViewVisible = getItemCount() == 1; 115 | } 116 | emptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE); 117 | } 118 | } 119 | 120 | /** 121 | * 返回item总数(包含顶部Header、底部Footer、分组hader和分组footer以及分组item内容) 122 | */ 123 | @Override 124 | public int getItemCount() { 125 | if (hasHeader()) { 126 | return count + 2; 127 | } else { 128 | return count + 1; 129 | } 130 | } 131 | 132 | private void setupPosition() { 133 | count = countItems();//计算出item的总数量 134 | setupArrays(count);//得到item的总数量后,初始化几个数组:初始化与position相对应的section数组,初始化与section相对应的position 135 | // 的数组,初始化当前位置是否是一个Header的数组,初始化当前位置是否是一个Footer的数组 136 | calculatePositions();//通过计算每个item的位置信息,将上一步初始化后的数组填充数据,最终这几个数组保存了每个位置的item 137 | // 的状态信息,即:是否是header,是否是footer,所在的position是多少,所在的section是多少 138 | } 139 | 140 | /** 141 | * 计算item的总数量 142 | * 143 | * @return 144 | */ 145 | private int countItems() { 146 | int count = 0; 147 | int sections = getSectionCount(); 148 | 149 | for (int i = 0; i < sections; i++) { 150 | count += 1 + getItemCountForSection(i) + (hasFooterInSection(i) ? 1 : 0); 151 | } 152 | return count; 153 | } 154 | 155 | /** 156 | * 通过item的总数量,初始化几个数组:初始化与position相对应的section数组, 157 | * 初始化与section相对应的position的数组,初始化当前位置是否是一个Header的数组, 158 | * 初始化当前位置是否是一个Footer的数组 159 | * 160 | * @param count 161 | */ 162 | private void setupArrays(int count) { 163 | sectionForPosition = new int[count]; 164 | positionWithinSection = new int[count]; 165 | isHeader = new boolean[count]; 166 | isFooter = new boolean[count]; 167 | } 168 | 169 | /** 170 | * 通过计算每个item的位置信息,将上一步初始化后的数组填充数据, 171 | * 最终这几个数组保存了每个位置的item的状态信息,即:是否是header,是否是footer, 172 | * 所在的position是多少,所在的section是多少 173 | */ 174 | private void calculatePositions() { 175 | int sections = getSectionCount(); 176 | int index = 0; 177 | 178 | for (int i = 0; i < sections; i++) { 179 | setupItems(index, true, false, i, 0); 180 | index++; 181 | 182 | for (int j = 0; j < getItemCountForSection(i); j++) { 183 | setupItems(index, false, false, i, j); 184 | index++; 185 | } 186 | 187 | if (hasFooterInSection(i)) { 188 | setupItems(index, false, true, i, 0); 189 | index++; 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * 保存每个位置对应的数据信息 196 | * 197 | * @param index 从0开始的每个最小单位所在的位置,从0开始,到count结束 198 | * @param isHeader 所在index位置的item是否是header 199 | * @param isFooter 所在index位置的item是否是footer 200 | * @param section 所在index位置的item对应的section 201 | * @param position 所在index位置的item对应的position 202 | */ 203 | private void setupItems(int index, boolean isHeader, boolean isFooter, int section, int 204 | position) { 205 | this.isHeader[index] = isHeader; 206 | this.isFooter[index] = isFooter; 207 | sectionForPosition[index] = section; 208 | positionWithinSection[index] = position; 209 | } 210 | 211 | @Override 212 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 213 | RecyclerView.ViewHolder viewHolder; 214 | if (viewType == TYPE_EMPTY) { 215 | viewHolder = new EmptyViewHolder(emptyView); 216 | } else { 217 | if (isSectionHeaderViewType(viewType)) { 218 | viewHolder = onCreateSectionHeaderViewHolder(parent, viewType); 219 | } else if (isSectionFooterViewType(viewType)) { 220 | viewHolder = onCreateSectionFooterViewHolder(parent, viewType); 221 | } else if (isFooterViewType(viewType)) { 222 | viewHolder = onCreateFooterViewHolder(parent, viewType); 223 | } else if (isHeaderViewType(viewType)) { 224 | viewHolder = onCreateHeaderViewHolder(parent, viewType); 225 | } else { 226 | viewHolder = onCreateItemViewHolder(parent, viewType); 227 | } 228 | } 229 | return viewHolder; 230 | } 231 | 232 | @Override 233 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 234 | if (emptyViewVisible) {//此时数据集为空,需要设置空布局 235 | } else { 236 | setViewHolder(holder, position); 237 | } 238 | } 239 | 240 | private void setViewHolder(RecyclerView.ViewHolder holder, final int position) { 241 | if (hasHeader()) {//如果整个列表有header 242 | if (position == 0) { 243 | onBindHeaderViewHolder((RH) holder); 244 | } else if (position + 1 < getItemCount()) { 245 | final int section = sectionForPosition[position - 1]; 246 | int index = positionWithinSection[position - 1]; 247 | if (isSectionHeaderPosition(position - 1)) {//当前位置是分组header 248 | onBindSectionHeaderViewHolder((H) holder, section); 249 | holder.itemView.setOnClickListener(new View.OnClickListener() { 250 | @Override 251 | public void onClick(View v) { 252 | onSectionHeaderClickListener.onSectionHeaderClick(section); 253 | } 254 | }); 255 | 256 | } else if (isSectionFooterPosition(position - 1)) {//当前位置是分组的footer 257 | 258 | onBindSectionFooterViewHolder((F) holder, section); 259 | holder.itemView.setOnClickListener(new View.OnClickListener() { 260 | @Override 261 | public void onClick(View v) { 262 | if (onSectionFooterClickListener != null) { 263 | onSectionFooterClickListener.onSectionFooterClick(section); 264 | } 265 | } 266 | }); 267 | 268 | } else {//当前位置是组内item 269 | onBindItemViewHolder((VH) holder, section, index); 270 | 271 | holder.itemView.setOnClickListener(new View.OnClickListener() { 272 | @Override 273 | public void onClick(View v) { 274 | onItemClickListener.onItemClick(section, position - 1); 275 | } 276 | }); 277 | 278 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { 279 | @Override 280 | public boolean onLongClick(View v) { 281 | if (onItemLongClickListener != null) { 282 | onItemLongClickListener.onItemLongClick(section, position - 1); 283 | } 284 | return true; 285 | } 286 | }); 287 | 288 | } 289 | } else {//当前位置是整个列表的footer 290 | onBindFooterViewHolder((FO) holder); 291 | } 292 | } else {//整个列表没有Header 293 | if (position + 1 < getItemCount()) { 294 | final int section = sectionForPosition[position]; 295 | int index = positionWithinSection[position]; 296 | if (isSectionHeaderPosition(position)) {//当前位置是分组Header 297 | onBindSectionHeaderViewHolder((H) holder, section); 298 | holder.itemView.setOnClickListener(new View.OnClickListener() { 299 | @Override 300 | public void onClick(View v) { 301 | if (onSectionHeaderClickListener != null) { 302 | onSectionHeaderClickListener.onSectionHeaderClick(section); 303 | } 304 | } 305 | }); 306 | 307 | } else if (isSectionFooterPosition(position)) {//当前位置是分组footer 308 | 309 | onBindSectionFooterViewHolder((F) holder, section); 310 | holder.itemView.setOnClickListener(new View.OnClickListener() { 311 | @Override 312 | public void onClick(View v) { 313 | if (onSectionFooterClickListener != null) { 314 | onSectionFooterClickListener.onSectionFooterClick(section); 315 | } 316 | } 317 | }); 318 | 319 | } else {//当前位置是分组的item 320 | onBindItemViewHolder((VH) holder, section, index); 321 | holder.itemView.setOnClickListener(new View.OnClickListener() { 322 | @Override 323 | public void onClick(View v) { 324 | if (onItemClickListener != null) { 325 | onItemClickListener.onItemClick(section, position); 326 | } 327 | } 328 | }); 329 | 330 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { 331 | @Override 332 | public boolean onLongClick(View v) { 333 | if (onItemLongClickListener != null) { 334 | onItemLongClickListener.onItemLongClick(section, position); 335 | } 336 | return true; 337 | } 338 | }); 339 | 340 | } 341 | } else {//当前位置是整个列表的footer 342 | onBindFooterViewHolder((FO) holder); 343 | } 344 | } 345 | } 346 | 347 | @Override 348 | public int getItemViewType(int position) { 349 | if (sectionForPosition == null) { 350 | setupPosition(); 351 | } 352 | if (emptyViewVisible) { 353 | return TYPE_EMPTY; 354 | } else { 355 | if (hasHeader()) { 356 | if (position == 0) { 357 | return getHeaderViewType(); 358 | } else if (position + 1 < getItemCount()) { 359 | int section = sectionForPosition[position - 1]; 360 | int index = positionWithinSection[position - 1]; 361 | if (isSectionHeaderPosition(position - 1)) { 362 | return getSectionHeaderViewType(section); 363 | } else if (isSectionFooterPosition(position - 1)) { 364 | return getSectionFooterViewType(section); 365 | } else { 366 | return getSectionItemViewType(section, index); 367 | } 368 | } 369 | return getFooterViewType(); 370 | } else { 371 | if (position + 1 < getItemCount()) { 372 | int section = sectionForPosition[position]; 373 | int index = positionWithinSection[position]; 374 | if (isSectionHeaderPosition(position)) { 375 | return getSectionHeaderViewType(section); 376 | } else if (isSectionFooterPosition(position)) { 377 | return getSectionFooterViewType(section); 378 | } else { 379 | return getSectionItemViewType(section, index); 380 | } 381 | } 382 | return getFooterViewType(); 383 | } 384 | } 385 | } 386 | 387 | 388 | /** 389 | * 是否是分组header 390 | * 391 | * @param viewType 392 | * @return 393 | */ 394 | protected boolean isSectionHeaderViewType(int viewType) { 395 | return viewType == TYPE_SECTION_HEADER; 396 | } 397 | 398 | /** 399 | * 是否是分组footer 400 | * 401 | * @param viewType 402 | * @return 403 | */ 404 | protected boolean isSectionFooterViewType(int viewType) { 405 | return viewType == TYPE_SECTION_FOOTER; 406 | } 407 | 408 | /** 409 | * 是否是列表的Header 410 | * 411 | * @param viewType 412 | * @return 413 | */ 414 | protected boolean isHeaderViewType(int viewType) { 415 | return viewType == TYPE_HEADER; 416 | } 417 | 418 | /** 419 | * 是否是列表的footer 420 | * 421 | * @param viewType 422 | * @return 423 | */ 424 | protected boolean isFooterViewType(int viewType) { 425 | return viewType == TYPE_FOOTER; 426 | } 427 | 428 | protected int getSectionHeaderViewType(int section) { 429 | return TYPE_SECTION_HEADER; 430 | } 431 | 432 | protected int getSectionFooterViewType(int section) { 433 | return TYPE_SECTION_FOOTER; 434 | } 435 | 436 | protected int getHeaderViewType() { 437 | return TYPE_HEADER; 438 | } 439 | 440 | protected int getFooterViewType() { 441 | return TYPE_FOOTER; 442 | } 443 | 444 | protected int getSectionItemViewType(int section, int position) { 445 | return TYPE_ITEM; 446 | } 447 | 448 | /** 449 | * 整个列表是否有Header 450 | */ 451 | protected abstract boolean hasHeader(); 452 | 453 | /** 454 | * 对应位置是否是一个分组header 455 | */ 456 | public boolean isSectionHeaderPosition(int position) { 457 | if (isHeader == null) { 458 | setupPosition(); 459 | } 460 | return isHeader[position]; 461 | } 462 | 463 | /** 464 | * 对应位置是否是一个分组footer 465 | */ 466 | public boolean isSectionFooterPosition(int position) { 467 | if (isFooter == null) { 468 | setupPosition(); 469 | } 470 | return isFooter[position]; 471 | } 472 | 473 | /** 474 | * 返回分组的数量 475 | * 476 | * @return 477 | */ 478 | protected abstract int getSectionCount(); 479 | 480 | /** 481 | * 返回当前分组的item数量 482 | * 483 | * @param section 484 | * @return 485 | */ 486 | protected abstract int getItemCountForSection(int section); 487 | 488 | /** 489 | * 当前分组是否有footer 490 | * 491 | * @param section 492 | * @return 493 | */ 494 | protected abstract boolean hasFooterInSection(int section); 495 | 496 | /** 497 | * 为分组header创建一个类型为H的ViewHolder 498 | * 499 | * @param parent 500 | * @param viewType 501 | * @return 502 | */ 503 | protected abstract H onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType); 504 | 505 | /** 506 | * 为分组footer创建一个类型为F的ViewHolder 507 | * 508 | * @param parent 509 | * @param viewType 510 | * @return 511 | */ 512 | protected abstract F onCreateSectionFooterViewHolder(ViewGroup parent, int viewType); 513 | 514 | /** 515 | * 为分组内容创建一个类型为VH的ViewHolder 516 | * 517 | * @param parent 518 | * @param viewType 519 | * @return 520 | */ 521 | protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType); 522 | 523 | /** 524 | * 为整个列表创建一个类型为RH的ViewHolder 525 | * 526 | * @param parent 527 | * @param viewType 528 | * @return 529 | */ 530 | protected abstract RH onCreateHeaderViewHolder(ViewGroup parent, int viewType); 531 | 532 | /** 533 | * 为整个列表创建一个类型为FO的ViewHolder 534 | * 535 | * @param parent 536 | * @param viewType 537 | * @return 538 | */ 539 | protected abstract FO onCreateFooterViewHolder(ViewGroup parent, int viewType); 540 | 541 | /** 542 | * 绑定分组的Header数据 543 | * 544 | * @param holder 545 | * @param section 546 | */ 547 | protected abstract void onBindSectionHeaderViewHolder(H holder, int section); 548 | 549 | /** 550 | * 绑定分组数据 551 | * 552 | * @param holder 553 | * @param section 554 | * @param position 555 | */ 556 | protected abstract void onBindItemViewHolder(VH holder, int section, int position); 557 | 558 | /** 559 | * 绑定Header数据 560 | * 561 | * @param holder 562 | */ 563 | protected abstract void onBindHeaderViewHolder(RH holder); 564 | 565 | /** 566 | * 绑定分组的footer数据 567 | * 568 | * @param holder 569 | * @param section 570 | */ 571 | protected abstract void onBindSectionFooterViewHolder(F holder, int section); 572 | 573 | /** 574 | * 绑定上拉加载footer(整个RecycerView的footer)数据 575 | * 576 | * @param holder 577 | */ 578 | protected void onBindFooterViewHolder(FO holder) { 579 | if (holder instanceof FooterHolder) { 580 | FooterHolder footerHolder = (FooterHolder) holder; 581 | switch (load_more_status) { 582 | case PULLUP_LOAD_MORE: 583 | footerHolder.tvFooter.setVisibility(View.VISIBLE); 584 | footerHolder.tvFooter.setText("上拉加载更多..."); 585 | break; 586 | case LOADING_MORE: 587 | footerHolder.tvFooter.setVisibility(View.VISIBLE); 588 | footerHolder.tvFooter.setText("正在加载数据..."); 589 | break; 590 | case LOADING_FINISH: 591 | footerHolder.tvFooter.setVisibility(View.VISIBLE); 592 | footerHolder.tvFooter.setText("没有更多数据"); 593 | break; 594 | default: 595 | footerHolder.tvFooter.setVisibility(View.GONE); 596 | break; 597 | } 598 | } else { 599 | onBindFooterOtherViewHolder(holder); 600 | } 601 | } 602 | 603 | /** 604 | * 当footer不是上拉刷新时,复写此方法,如:点击查看更多或者更复杂的布局等 605 | * 606 | * @param holder 607 | */ 608 | protected abstract void onBindFooterOtherViewHolder(FO holder); 609 | 610 | /** 611 | * @param status 612 | */ 613 | public void changeMoreStatus(int status) { 614 | load_more_status = status; 615 | notifyDataSetChanged(); 616 | } 617 | 618 | /** 619 | * 返回分组item的位置 620 | * 621 | * @param position 622 | * @return 623 | */ 624 | public int getItemPosition(int position) { 625 | return positionWithinSection[position]; 626 | } 627 | 628 | /** 629 | * 分组内的item点击回调 630 | */ 631 | public interface OnItemClickListener { 632 | void onItemClick(int section, int position); 633 | } 634 | 635 | 636 | /** 637 | * item长按回调 638 | */ 639 | public interface OnItemLongClickListener { 640 | void onItemLongClick(int section, int position); 641 | } 642 | 643 | /** 644 | * section的Header的点击回调 645 | */ 646 | public interface OnSectionHeaderClickListener { 647 | void onSectionHeaderClick(int section); 648 | } 649 | 650 | /** 651 | * section的Footer的点击回调 652 | */ 653 | public interface OnSectionFooterClickListener { 654 | void onSectionFooterClick(int section); 655 | } 656 | 657 | /** 658 | * 分组内子View点击事件回调,多了一个viewType,用以区分同一个item的不同的点击事件 659 | * 根据需求,需要时可实现此接口 660 | */ 661 | public interface OnChildClickListener { 662 | 663 | /** 664 | * @param position item position 665 | * @param viewType 点击的view的类型,调用时根据不同的view传入不同的值加以区分,如viewType=0表示进入下一级页面,viewType=1表示查看大图等 666 | */ 667 | void onChildClick(int position, int viewType); 668 | } 669 | 670 | public void setOnChildClickListener(OnChildClickListener onChildClickListener) { 671 | this.onChildClickListener = onChildClickListener; 672 | } 673 | 674 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 675 | this.onItemClickListener = onItemClickListener; 676 | } 677 | 678 | public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { 679 | this.onItemLongClickListener = onItemLongClickListener; 680 | } 681 | 682 | public void setOnSectionHeaderClickListener(OnSectionHeaderClickListener 683 | onSectionHeaderClickListener) { 684 | this.onSectionHeaderClickListener = onSectionHeaderClickListener; 685 | } 686 | 687 | public void setOnSectionFooterClickListener(OnSectionFooterClickListener 688 | onSectionFooterClickListener) { 689 | this.onSectionFooterClickListener = onSectionFooterClickListener; 690 | } 691 | 692 | } 693 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/adapter/SectionedSpanSizeLookup.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.adapter; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | 5 | /** 6 | * package: com.easyandroid.sectionadapter.adapter.SectionedSpanSizeLookup 7 | * author: gyc 8 | * description:分组每行显示数量的管理类 9 | * time: create at 2017/7/7 23:19 10 | */ 11 | 12 | public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup{ 13 | 14 | protected SectionedRecyclerViewAdapter adapter = null; 15 | protected GridLayoutManager layoutManager = null; 16 | 17 | public SectionedSpanSizeLookup(SectionedRecyclerViewAdapter adapter, GridLayoutManager layoutManager) { 18 | this.adapter = adapter; 19 | this.layoutManager = layoutManager; 20 | } 21 | 22 | @Override 23 | public int getSpanSize(int position) { 24 | if (adapter.hasHeader()) {//列表顶部有header 25 | if (position == 0) { 26 | return layoutManager.getSpanCount(); 27 | } else if (position + 1 < adapter.getItemCount()) { 28 | if (adapter.isSectionHeaderPosition(position -1) || adapter.isSectionFooterPosition(position -1)) { 29 | return layoutManager.getSpanCount(); 30 | } else { 31 | return 1; 32 | } 33 | } else { 34 | return layoutManager.getSpanCount(); 35 | } 36 | } else {//列表顶部没有header 37 | if (position + 1 < adapter.getItemCount()) { 38 | if (adapter.isSectionHeaderPosition(position) || adapter.isSectionFooterPosition(position)) { 39 | return layoutManager.getSpanCount(); 40 | } else { 41 | return 1; 42 | } 43 | } else { 44 | return layoutManager.getSpanCount(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/entity/TestEntity.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * package: com.easyandroid.sectionadapter.entity.TestEntity 7 | * author: gyc 8 | * description: 9 | * time: create at 2017/7/8 3:16 10 | */ 11 | 12 | public class TestEntity { 13 | 14 | private BodyBean body; 15 | 16 | public BodyBean getBody() { 17 | return body; 18 | } 19 | 20 | public void setBody(BodyBean body) { 21 | this.body = body; 22 | } 23 | 24 | public static class BodyBean { 25 | private List eList; 26 | 27 | public List getEList() { 28 | return eList; 29 | } 30 | 31 | public void setEList(List eList) { 32 | this.eList = eList; 33 | } 34 | 35 | public static class EListBean { 36 | 37 | private String picture; 38 | private String time; 39 | private String userName; 40 | private String content; 41 | private String browser; 42 | private List ePicture; 43 | 44 | public String getPicture() { 45 | return picture; 46 | } 47 | 48 | public void setPicture(String picture) { 49 | this.picture = picture; 50 | } 51 | 52 | public String getTime() { 53 | return time; 54 | } 55 | 56 | public void setTime(String time) { 57 | this.time = time; 58 | } 59 | 60 | public String getUserName() { 61 | return userName; 62 | } 63 | 64 | public void setUserName(String userName) { 65 | this.userName = userName; 66 | } 67 | 68 | public String getContent() { 69 | return content; 70 | } 71 | 72 | public void setContent(String content) { 73 | this.content = content; 74 | } 75 | 76 | public String getBrowser() { 77 | return browser; 78 | } 79 | 80 | public void setBrowser(String browser) { 81 | this.browser = browser; 82 | } 83 | 84 | public List getEPicture() { 85 | return ePicture; 86 | } 87 | 88 | public void setEPicture(List ePicture) { 89 | this.ePicture = ePicture; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/EmptyViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * package: com.easyandroid.sectionadapter.holder.EmptyViewHolder 8 | * author: gyc 9 | * description:空布局 10 | * time: create at 2017/7/11 20:40 11 | */ 12 | 13 | public class EmptyViewHolder extends RecyclerView.ViewHolder { 14 | public EmptyViewHolder(View itemView) { 15 | super(itemView); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/FooterHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | import com.easyandroid.sectionadapter.R; 8 | 9 | /** 10 | * package: com.easyandroid.sectionadapter.holder.FooterHolder 11 | * author: gyc 12 | * description:上拉加载的footer 13 | * time: create at 2017/7/7 20:50 14 | */ 15 | 16 | public class FooterHolder extends RecyclerView.ViewHolder { 17 | public TextView tvFooter; 18 | 19 | public FooterHolder(View itemView) { 20 | super(itemView); 21 | initView(); 22 | } 23 | 24 | private void initView() { 25 | tvFooter = (TextView) itemView.findViewById(R.id.tv_footer); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/TestSectionBodyHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.LinearLayout; 7 | 8 | import com.easyandroid.sectionadapter.R; 9 | 10 | /** 11 | * package: com.easyandroid.sectionadapter.holder.TestSectionBodyHolder 12 | * author: gyc 13 | * description:SectionBody的holder 14 | * time: create at 2017/7/8 3:00 15 | */ 16 | 17 | public class TestSectionBodyHolder extends RecyclerView.ViewHolder{ 18 | 19 | public LinearLayout llRoot; 20 | public ImageView imgEvaluate; 21 | 22 | public TestSectionBodyHolder(View itemView) { 23 | super(itemView); 24 | initView(); 25 | } 26 | 27 | private void initView() { 28 | llRoot = (LinearLayout) itemView.findViewById(R.id.ll_root); 29 | imgEvaluate = (ImageView) itemView.findViewById(R.id.img_evaluate); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/TestSectionFooterHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.LinearLayout; 7 | import android.widget.TextView; 8 | 9 | import com.easyandroid.sectionadapter.R; 10 | 11 | /** 12 | * package: com.easyandroid.sectionadapter.holder.TestSectionFooterHolder 13 | * author: gyc 14 | * description:SectionFooter的holder 15 | * time: create at 2017/7/8 3:01 16 | */ 17 | 18 | public class TestSectionFooterHolder extends RecyclerView.ViewHolder{ 19 | 20 | public TextView tvLookNum,tvLikeNum, tvEvaluateNum; 21 | public LinearLayout llZan, llNum; 22 | public ImageView imgZan; 23 | 24 | public TestSectionFooterHolder(View itemView) { 25 | super(itemView); 26 | initView(); 27 | } 28 | 29 | private void initView() { 30 | tvLookNum = (TextView) itemView.findViewById(R.id.tv_look_num); 31 | llNum = (LinearLayout) itemView.findViewById(R.id.ll_evaluate); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/TestSectionHeaderHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | 8 | import com.easyandroid.sectionadapter.R; 9 | 10 | /** 11 | * package: com.easyandroid.sectionadapter.holder.TestSectionHeaderHolder 12 | * author: gyc 13 | * description:SectionHeader的holder 14 | * time: create at 2017/7/8 3:01 15 | */ 16 | 17 | public class TestSectionHeaderHolder extends RecyclerView.ViewHolder { 18 | public TextView tvNike, tvEvaluate, tvDate; 19 | public ImageView imgHead; 20 | 21 | public TestSectionHeaderHolder(View itemView) { 22 | super(itemView); 23 | initView(); 24 | } 25 | 26 | private void initView() { 27 | tvNike = (TextView) itemView.findViewById(R.id.tv_nike_name); 28 | tvEvaluate = (TextView) itemView.findViewById(R.id.tv_evaluate); 29 | tvDate = (TextView) itemView.findViewById(R.id.tv_date); 30 | imgHead = (ImageView) itemView.findViewById(R.id.img_head); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/holder/TypeAbstractViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * package: com.easyandroid.sectionadapter.holder.TypeAbstractViewHolder 8 | * author: gyc 9 | * description: 10 | * time: create at 2017/8/3 22:21 11 | */ 12 | 13 | public abstract class TypeAbstractViewHolder extends RecyclerView.ViewHolder { 14 | 15 | public TypeAbstractViewHolder(View itemView) { 16 | super(itemView); 17 | } 18 | 19 | public abstract void bindHolder(T entity); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/listener/LoadMoreListener.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.listener; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | /** 8 | * package: com.easyandroid.sectionadapter.listener.LoadMoreListener 9 | * author: gyc 10 | * description:继承Recyclerview的滚动事件,实现上拉加载 11 | * time: create at 2017/7/7 22:23 12 | */ 13 | 14 | public abstract class LoadMoreListener extends RecyclerView.OnScrollListener { 15 | 16 | public boolean isLoading = false;//记录正在加载的状态,防止多次请求 17 | protected int lastItemPosition; 18 | private int topOffset = 0;//列表顶部容差值 19 | private int bottomOffset = 0;//列表底部容差值 20 | 21 | protected GridLayoutManager gridLayoutManager; 22 | 23 | public LoadMoreListener(GridLayoutManager gridLayoutManager) { 24 | this.gridLayoutManager = gridLayoutManager; 25 | } 26 | 27 | @Override 28 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 29 | super.onScrollStateChanged(recyclerView, newState); 30 | if (isFullAScreen(recyclerView)) { 31 | //查找最后一个可见的item的position 32 | lastItemPosition = gridLayoutManager.findLastVisibleItemPosition(); 33 | if (newState == RecyclerView.SCROLL_STATE_IDLE && lastItemPosition + 1 == 34 | gridLayoutManager.getItemCount()) { 35 | if (!isLoading) { 36 | onLoadMore(); 37 | } 38 | } 39 | } 40 | } 41 | 42 | @Override 43 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 44 | super.onScrolled(recyclerView, dx, dy); 45 | } 46 | 47 | /** 48 | * 检查是否满一屏 49 | * 50 | * @param recyclerView 51 | * @return 52 | */ 53 | public boolean isFullAScreen(RecyclerView recyclerView) { 54 | //获取item总个数,一般用mAdapter.getItemCount(),用mRecyclerView.getLayoutManager().getItemCount()也可以 55 | //获取当前可见的item view的个数,这个数字是不固定的,随着recycleview的滑动会改变, 56 | // 比如有的页面显示出了6个view,那这个数字就是6。此时滑一下,第一个view出去了一半,后边又加进来半个view,此时getChildCount() 57 | // 就是7。所以这里可见item view的个数,露出一半也算一个。 58 | int visiableItemCount = recyclerView.getChildCount(); 59 | if (visiableItemCount > 0) { 60 | View lastChildView = recyclerView.getChildAt(visiableItemCount - 1); 61 | //获取第一个childView 62 | View firstChildView = recyclerView.getChildAt(0); 63 | int top = firstChildView.getTop(); 64 | int bottom = lastChildView.getBottom(); 65 | //recycleView显示itemView的有效区域的bottom坐标Y 66 | int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom() + bottomOffset; 67 | //recycleView显示itemView的有效区域的top坐标Y 68 | int topEdge = recyclerView.getPaddingTop() + topOffset; 69 | //第一个view的顶部小于top边界值,说明第一个view已经部分或者完全移出了界面 70 | //最后一个view的底部小于bottom边界值,说明最后一个view已经完全显示在界面 71 | //若满足这两个条件,说明所有子view已经填充满了recycleView,recycleView可以"真正地"滑动 72 | if (bottom <= bottomEdge && top < topEdge) { 73 | //满屏的recyceView 74 | return true; 75 | } 76 | return false; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | public abstract void onLoadMore(); 83 | 84 | public void setTopOffset(int topOffset) { 85 | this.topOffset = topOffset; 86 | } 87 | 88 | public void setBottomOffset(int bottomOffset) { 89 | this.bottomOffset = bottomOffset; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/listener/RecycleViewScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.listener; 2 | 3 | import android.support.annotation.IntRange; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * package: com.easyandroid.sectionadapter.listener.RecycleViewScrollHelper 10 | * author: gyc 11 | * description:RecyclerView滑动的各种情况的辅助类 12 | * time: create at 2017/7/7 23:13 13 | */ 14 | 15 | public class RecycleViewScrollHelper extends RecyclerView.OnScrollListener{ 16 | 17 | private RecyclerView mRvScroll = null; 18 | private OnScrollDirectionChangedListener mScrollDirectionChangedListener = null; 19 | //滑动位置变动的监听事件 20 | private OnScrollPositionChangedListener mScrollPositionChangedListener = null; 21 | //是否同时检测滑动到顶部及底部 22 | private boolean mIsCheckTopBottomTogether = false; 23 | //检测滑动顶部/底部的优先顺序,默认先检测滑动到底部 24 | private boolean mIsCheckTopFirstBottomAfter = false; 25 | //检测底部滑动时是否检测满屏状态 26 | private boolean mIsCheckBottomFullRecycle = false; 27 | //检测顶部滑动时是否检测满屏状态 28 | private boolean mIsCheckTopFullRecycle = false; 29 | //顶部满屏检测时允许的容差值 30 | private int mTopOffsetFaultTolerance = 0; 31 | //底部满屏检测时允许的容差值 32 | private int mBottomOffsetFaultTolerance = 0; 33 | 34 | private int mScrollDx = 0; 35 | private int mScrollDy = 0; 36 | 37 | /** 38 | * recycleView的滑动监听事件,用于检测是否滑动到顶部或者滑动到底部. 39 | * 40 | * @param listener {@link OnScrollPositionChangedListener}滑动位置变动监听事件 41 | */ 42 | public RecycleViewScrollHelper(OnScrollPositionChangedListener listener) { 43 | mScrollPositionChangedListener = listener; 44 | } 45 | 46 | @Override 47 | public void onScrollStateChanged(RecyclerView recyclerView, 48 | int newState) { 49 | if (mScrollPositionChangedListener == null || recyclerView.getAdapter() == null || recyclerView.getChildCount() <= 0) { 50 | return; 51 | } 52 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 53 | if (layoutManager instanceof LinearLayoutManager) { 54 | LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager; 55 | int lastItemPosition = linearManager.findLastVisibleItemPosition(); 56 | int firstItemPosition = linearManager.findFirstVisibleItemPosition(); 57 | RecyclerView.Adapter adapter = recyclerView.getAdapter(); 58 | if (newState == RecyclerView.SCROLL_STATE_IDLE) { 59 | //判断顶部/底部检测的优先顺序 60 | if (!mIsCheckTopFirstBottomAfter) { 61 | //先检测底部 62 | if (this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount())) { 63 | //若检测滑动到底部时,判断是否需要同时检测滑动到顶部 64 | if (mIsCheckTopBottomTogether) { 65 | //检测是否滑动到顶部 66 | this.checkIfScrollToTop(recyclerView, firstItemPosition); 67 | //不管是否滑动到顶部,已经触发了滑动到底部,所以直接返回,否则会调用滑动到未知位置的 68 | return; 69 | } else { 70 | //若不需要同时检测,直接返回 71 | return; 72 | } 73 | } else if (this.checkIfScrollToTop(recyclerView, firstItemPosition)) { 74 | //当未检测滑动到底部时,再检测是否滑动到顶部 75 | return; 76 | } 77 | } else { 78 | //先检测是否滑动到顶部 79 | if (this.checkIfScrollToTop(recyclerView, firstItemPosition)) { 80 | if (mIsCheckTopBottomTogether) { 81 | //检测是否滑动到底部 82 | this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount()); 83 | return; 84 | } else { 85 | //若不需要同时检测,直接返回 86 | return; 87 | } 88 | } else if (this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount())) { 89 | //当未检测滑动到底部时,再检测是否滑动到底部 90 | return; 91 | } 92 | } 93 | } 94 | } 95 | //其它任何情况 96 | mScrollPositionChangedListener.onScrollToUnknown(false, false); 97 | } 98 | 99 | /** 100 | * 检测是否滑动到了顶部item并回调事件 101 | * 102 | * @param recyclerView 103 | * @param firstItemPosition 第一个可见itemView的position 104 | * @return 105 | */ 106 | private boolean checkIfScrollToTop(RecyclerView recyclerView, int firstItemPosition) { 107 | if (firstItemPosition == 0) { 108 | if (mIsCheckTopFullRecycle) { 109 | int childCount = recyclerView.getChildCount(); 110 | View firstChild = recyclerView.getChildAt(0); 111 | View lastChild = recyclerView.getChildAt(childCount - 1); 112 | int top = firstChild.getTop(); 113 | int bottom = lastChild.getBottom(); 114 | //recycleView显示itemView的有效区域的top坐标Y 115 | int topEdge = recyclerView.getPaddingTop() - mTopOffsetFaultTolerance; 116 | //recycleView显示itemView的有效区域的bottom坐标Y 117 | int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom() - mBottomOffsetFaultTolerance; 118 | //第一个view的顶部大于top边界值,说明第一个view已经完全显示在顶部 119 | //同时最后一个view的底部应该小于bottom边界值,说明最后一个view的底部已经超出显示范围,部分或者完全移出了界面 120 | if (top >= topEdge && bottom > bottomEdge) { 121 | mScrollPositionChangedListener.onScrollToTop(); 122 | return true; 123 | } else { 124 | mScrollPositionChangedListener.onScrollToUnknown(true, false); 125 | } 126 | } else { 127 | mScrollPositionChangedListener.onScrollToTop(); 128 | return true; 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | /** 135 | * 检测是否滑动到底部item并回调事件 136 | * 137 | * @param recyclerView 138 | * @param lastItemPosition 最后一个可见itemView的position 139 | * @param itemCount adapter的itemCount 140 | * @return 141 | */ 142 | private boolean checkIfScrollToBottom(RecyclerView recyclerView, int lastItemPosition, int itemCount) { 143 | if (lastItemPosition + 1 == itemCount) { 144 | //是否进行满屏的判断处理 145 | //未满屏的情况下将永远不会被回调滑动到低部或者顶部 146 | if (mIsCheckBottomFullRecycle) { 147 | int childCount = recyclerView.getChildCount(); 148 | //获取最后一个childView 149 | View lastChildView = recyclerView.getChildAt(childCount - 1); 150 | //获取第一个childView 151 | View firstChildView = recyclerView.getChildAt(0); 152 | int top = firstChildView.getTop(); 153 | int bottom = lastChildView.getBottom(); 154 | //recycleView显示itemView的有效区域的bottom坐标Y 155 | int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom() + mBottomOffsetFaultTolerance; 156 | //recycleView显示itemView的有效区域的top坐标Y 157 | int topEdge = recyclerView.getPaddingTop() + mTopOffsetFaultTolerance; 158 | //第一个view的顶部小于top边界值,说明第一个view已经部分或者完全移出了界面 159 | //最后一个view的底部小于bottom边界值,说明最后一个view已经完全显示在界面 160 | //若不处理这种情况,可能会存在recycleView高度足够高时,itemView数量很少无法填充一屏,但是滑动到最后一项时依然会发生回调 161 | //此时其实并不需要任何刷新操作的 162 | if (bottom <= bottomEdge && top < topEdge) { 163 | mScrollPositionChangedListener.onScrollToBottom(); 164 | return true; 165 | } else { 166 | mScrollPositionChangedListener.onScrollToUnknown(false, true); 167 | } 168 | } else { 169 | mScrollPositionChangedListener.onScrollToBottom(); 170 | return true; 171 | } 172 | } 173 | return false; 174 | } 175 | 176 | @Override 177 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 178 | if (mScrollDirectionChangedListener != null) { 179 | if (dx == 0 && dy == 0) { 180 | mScrollDirectionChangedListener.onScrollDirectionChanged(0, 0); 181 | } else if (dx == 0) { 182 | boolean isUp = dy > 0; 183 | boolean isBeenUp = mScrollDy > 0; 184 | if (isUp != isBeenUp) { 185 | mScrollDx = dx; 186 | mScrollDy = dy; 187 | mScrollDirectionChangedListener.onScrollDirectionChanged(dx, dy); 188 | } 189 | } else if (dy == 0) { 190 | boolean isLeft = dx > 0; 191 | boolean isBeenLeft = mScrollDx > 0; 192 | if (isLeft != isBeenLeft) { 193 | mScrollDx = dx; 194 | mScrollDy = dy; 195 | mScrollDirectionChangedListener.onScrollDirectionChanged(dx, dy); 196 | } 197 | } 198 | } 199 | } 200 | 201 | //重置数据 202 | private void reset() { 203 | mScrollDx = 0; 204 | mScrollDy = 0; 205 | } 206 | 207 | /** 208 | * 关联recycleView,当关联新的recycleView时,会自动移除上一个关联recycleView 209 | * 210 | * @param recyclerView 211 | */ 212 | public void attachToRecycleView(RecyclerView recyclerView) { 213 | if (recyclerView != mRvScroll) { 214 | unAttachToRecycleView(); 215 | mRvScroll = recyclerView; 216 | if (recyclerView != null) { 217 | recyclerView.addOnScrollListener(this); 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * 移除与recycleView的绑定 224 | */ 225 | public void unAttachToRecycleView() { 226 | if (mRvScroll != null) { 227 | mRvScroll.removeOnScrollListener(this); 228 | } 229 | this.reset(); 230 | } 231 | 232 | /** 233 | * 设置滑动方向改变时的回调接口 234 | * 235 | * @param listener 236 | */ 237 | public void setScrollDirectionChangedListener(OnScrollDirectionChangedListener listener) { 238 | mScrollDirectionChangedListener = listener; 239 | } 240 | 241 | /** 242 | * 设置顶部允许偏移的容差值,此值仅在允许检测满屏时有效,当{@link #setCheckIfItemViewFullRecycleViewForTop(boolean)}设置为true 或者{@link #setCheckIfItemViewFullRecycleViewForBottom(boolean)}设置为true 时有效.
243 | * 在检测底部滑动时,对顶部的检测会添加此容差值(更容易判断当前第一项childView已超出recycleView的显示范围),用于协助判断是否滑动到底部. 244 | * 在检测顶部滑动时,对顶部的检测会添加此容差值(更容易判断为滑动到了顶部) 245 | * 246 | * @param offset 容差值,此值必须为0或正数 247 | */ 248 | public void setTopOffsetFaultTolerance(@IntRange(from = 0) int offset) { 249 | mTopOffsetFaultTolerance = offset; 250 | } 251 | 252 | /** 253 | * 设置顶部允许偏移的容差值,此值仅在允许检测满屏时有效,当{@link #setCheckIfItemViewFullRecycleViewForTop(boolean)}设置为true 或者{@link #setCheckIfItemViewFullRecycleViewForBottom(boolean)}设置为true 时有效.
254 | * 在检测底部滑动时,对底部的检测会添加此容差值(更容易判断当前最后一项childView已超出recycleView的显示范围),用于协助判断是否滑动到顶部. 255 | * 在检测顶部滑动时,对底部的检测会添加此容差值(更容易判断为滑动到了底部) 256 | * 257 | * @param offset 容差值,此值必须为0或正数 258 | */ 259 | public void setBottomFaultTolerance(@IntRange(from = 0) int offset) { 260 | mBottomOffsetFaultTolerance = offset; 261 | } 262 | 263 | /** 264 | * 设置是否需要检测recycleView是否为满屏的itemView时才回调事件.
265 | *

266 | * 当RecycleView的childView数量很少时,有可能RecycleView已经显示出所有的itemView,此时不存在向上滑动的可能.
267 | * 若设置当前值为true时,只有在RecycleView无法完全显示所有的itemView时,才会回调滑动到顶部的事件;否则将不处理;
268 | * 若设置为false则反之,不管任何时候只要滑动并顶部item显示时都会回调滑动事件 269 | * 270 | * @param isNeedToCheck true为当检测是否满屏显示;false不检测,直接回调事件 271 | */ 272 | public void setCheckIfItemViewFullRecycleViewForTop(boolean isNeedToCheck) { 273 | mIsCheckTopFullRecycle = isNeedToCheck; 274 | } 275 | 276 | /** 277 | * 设置是否需要检测recycleView是否为满屏的itemView时才回调事件.
278 | *

279 | * 当RecycleView的childView数量很少时,有可能RecycleView已经显示o出所有的itemView,此时不存在向下滑动的可能. 280 | * 若设置当前值为true时,只有在RecycleView无法完全显示所有的itemView时,才会回调滑动到底部的事件;否则将不处理; 281 | * 若设置为false则反之,不管任何时候只要滑动到底部都会回调滑动事件 282 | * 283 | * @param isNeedToCheck true为当检测是否满屏显示;false不检测,直接回调事件 284 | */ 285 | public void setCheckIfItemViewFullRecycleViewForBottom(boolean isNeedToCheck) { 286 | mIsCheckBottomFullRecycle = isNeedToCheck; 287 | } 288 | 289 | /** 290 | * 设置是否先检测滑动到哪里.默认为false,先检测滑动到底部 291 | * 292 | * @param isTopFirst true为先检测滑动到顶部再检测滑动到底部;false为先检测滑动到底部再滑动到顶部 293 | */ 294 | public void setCheckScrollToTopFirstBottomAfter(boolean isTopFirst) { 295 | mIsCheckTopFirstBottomAfter = isTopFirst; 296 | } 297 | 298 | /** 299 | * 设置是否同时检测滑动到顶部及底部,默认为false,先检测到任何一个状态都会直接返回,不会再继续检测其它状态 300 | * 301 | * @param isCheckTogether true为两种状态都检测,即使已经检测到其中某种状态了.false为先检测到任何一种状态时将不再检测另一种状态 302 | */ 303 | public void setCheckScrollToTopBottomTogether(boolean isCheckTogether) { 304 | mIsCheckTopBottomTogether = isCheckTogether; 305 | } 306 | 307 | /** 308 | * 滑动位置改变监听事件,滑动到顶部/底部或者非以上两个位置时 309 | */ 310 | public interface OnScrollPositionChangedListener { 311 | /** 312 | * 滑动到顶部的回调事件 313 | */ 314 | public void onScrollToTop(); 315 | 316 | /** 317 | * 滑动到底部的回调事件 318 | */ 319 | public void onScrollToBottom(); 320 | 321 | /** 322 | * 滑动到未知位置的回调事件 323 | * 324 | * @param isTopViewVisible 当前位置顶部第一个itemView是否可见,这里是指adapter中的最后一个itemView 325 | * @param isBottomViewVisible 当前位置底部最后一个itemView是否可见,这里是指adapter中的最后一个itemView 326 | */ 327 | public void onScrollToUnknown(boolean isTopViewVisible, boolean isBottomViewVisible); 328 | } 329 | 330 | /** 331 | * 滑动方向改变时监听事件 332 | */ 333 | public interface OnScrollDirectionChangedListener { 334 | /** 335 | * 滑动方向改变时监听事件,当两个参数值都为0时,数据变动重新layout 336 | * 337 | * @param scrollVertical 竖直方向的滑动方向,向上<0,向下>0,不动(水平滑动时)=0 338 | * @param scrollHorizontal 水平方向的滑动方向,向左<0,向右>0,不动(竖直滑动时)=0 339 | */ 340 | public void onScrollDirectionChanged(int scrollHorizontal, int scrollVertical); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/mvp/base/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.mvp.base; 2 | 3 | /** 4 | * package: com.easyandroid.sectionadapter.mvp.base.BasePresenter 5 | * author: gyc 6 | * description: 7 | * time: create at 2017/7/8 9:50 8 | */ 9 | 10 | public interface BasePresenter { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/mvp/base/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.mvp.base; 2 | 3 | /** 4 | * package: com.easyandroid.sectionadapter.mvp.base.BaseView 5 | * author: gyc 6 | * description: 7 | * time: create at 2017/7/8 9:50 8 | */ 9 | 10 | public interface BaseView { 11 | void showLoading(String msg); 12 | void hideLoading(); 13 | void showError(String errorMsg); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/mvp/base/Module.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.mvp.base; 2 | 3 | import com.easyandroid.sectionadapter.entity.TestEntity; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * package: com.easyandroid.sectionadapter.mvp.base.Module 9 | * author: gyc 10 | * description: 11 | * time: create at 2017/7/8 9:51 12 | */ 13 | 14 | public class Module { 15 | 16 | public interface View extends BaseView{ 17 | void updateList(int type, List datas); 18 | } 19 | 20 | public interface Presenter extends BasePresenter{ 21 | void loadData(int loadType); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/mvp/model/TestModel.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.mvp.model; 2 | 3 | /** 4 | * package: com.easyandroid.sectionadapter.mvp.model.TestModel 5 | * author: gyc 6 | * description: 7 | * time: create at 2017/7/8 9:46 8 | */ 9 | 10 | public class TestModel { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/mvp/presenter/TestPresenter.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.mvp.presenter; 2 | 3 | import com.easyandroid.sectionadapter.entity.TestEntity; 4 | import com.easyandroid.sectionadapter.mvp.base.Module; 5 | import com.easyandroid.sectionadapter.util.DatasUtil; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * package: com.easyandroid.sectionadapter.mvp.presenter.TestPresenter 11 | * author: gyc 12 | * description: 13 | * time: create at 2017/7/8 9:53 14 | */ 15 | 16 | public class TestPresenter implements Module.Presenter{ 17 | 18 | private Module.View view; 19 | 20 | public TestPresenter(Module.View view) { 21 | this.view = view; 22 | } 23 | 24 | @Override 25 | public void loadData(int loadType) { 26 | List datas = DatasUtil.createDatas(); 27 | if(view!=null){ 28 | view.updateList(loadType, datas); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/util/DatasUtil.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.util; 2 | 3 | import com.easyandroid.sectionadapter.entity.TestEntity; 4 | import com.easytools.tools.TimeUtil; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * package: com.easyandroid.sectionadapter.util.DatasUtil 11 | * author: gyc 12 | * description: 13 | * time: create at 2017/7/8 9:58 14 | */ 15 | 16 | public class DatasUtil { 17 | 18 | static String url1 = "http://g.hiphotos.baidu" + 19 | ".com/image/pic/item/4b90f603738da977c76ab6fab451f8198718e39e.jpg"; 20 | static String url2 = "http://www.zjito.com/upload/resources/image/2015/11/21/8577adeb-c075-409d-b910-9d29137f8b84_720x1500.jpg?1483574072000"; 21 | 22 | 23 | public static List createDatas() { 24 | List mDatas = new ArrayList<>(); 25 | for (int i = 0; i < 6; i++) { 26 | TestEntity.BodyBean.EListBean bean = new TestEntity.BodyBean.EListBean(); 27 | List urls = new ArrayList<>(); 28 | bean.setPicture(url1); 29 | bean.setContent("炎热的夏日,深深的森林里,据说,有妖怪"); 30 | bean.setTime(TimeUtil.getTimeString()); 31 | bean.setBrowser("103"); 32 | bean.setUserName("WD"); 33 | urls.add(url2); 34 | urls.add(url2); 35 | urls.add(url2); 36 | urls.add(url2); 37 | urls.add(url2); 38 | urls.add(url2); 39 | bean.setEPicture(urls); 40 | mDatas.add(bean); 41 | } 42 | return mDatas; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/util/ListUtil.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.util; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * package: com.easyandroid.sectionadapter.util.ListUtil 7 | * author: gyc 8 | * description: 9 | * time: create at 2017/7/8 3:24 10 | */ 11 | 12 | public class ListUtil { 13 | /** 14 | * 判断list数据是否为空 15 | * @param list 16 | * @param

17 | * @return 18 | */ 19 | public static

boolean isEmpty(List

list) { 20 | return list == null || list.isEmpty(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/easyandroid/sectionadapter/widgets/SectionedGridDivider.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter.widgets; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.View; 11 | 12 | import com.easyandroid.sectionadapter.adapter.SectionedRecyclerViewAdapter; 13 | 14 | /** 15 | * package: com.easyandroid.sectionadapter.widgets.SectionedGridDivider 16 | * author: gyc 17 | * description:分组分割线 18 | * time: create at 2017/7/10 20:49 19 | */ 20 | 21 | public class SectionedGridDivider extends RecyclerView.ItemDecoration { 22 | 23 | private Drawable mDividerDrawable; 24 | private int mDividerHeight = 1;//分割线的高度,默认为1 25 | private Paint mDividerPaint;//分割线的颜色 26 | 27 | /** 28 | * 使用自定义资源文件 29 | * 30 | * @param context 31 | * @param drawableId 32 | */ 33 | public SectionedGridDivider(Context context, int drawableId) { 34 | this.mDividerDrawable = ContextCompat.getDrawable(context, drawableId); 35 | mDividerHeight = mDividerDrawable.getIntrinsicHeight(); 36 | } 37 | 38 | /** 39 | * 使用画笔画出分割线 40 | * 41 | * @param context 42 | * @param dividerHeight 43 | * @param dividerColor 44 | */ 45 | public SectionedGridDivider(Context context, int dividerHeight, int dividerColor) { 46 | this.mDividerHeight = dividerHeight; 47 | mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 48 | mDividerPaint.setColor(dividerColor); 49 | mDividerPaint.setStyle(Paint.Style.FILL); 50 | } 51 | 52 | 53 | @Override 54 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State 55 | state) { 56 | super.getItemOffsets(outRect, view, parent, state); 57 | int totalCount = parent.getAdapter().getItemCount(); 58 | int itemPosition = parent.getChildAdapterPosition(view); 59 | if (isDraw(parent, view, totalCount)) { 60 | if (itemPosition == 0) { 61 | outRect.set(0, 0, 0, 0); 62 | } else { 63 | outRect.set(0, mDividerHeight, 0, 0); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 是否可以绘制分割线 70 | * @param parent 当前的RecyclerView 71 | * @param itemView 当前的内容项 72 | * @param totalCount 适配器的item总数,可能大于RecyclerView的item总数 73 | * @return 74 | */ 75 | private boolean isDraw(RecyclerView parent, View itemView, int totalCount) { 76 | int itemPosition = parent.getChildAdapterPosition(itemView); 77 | if (totalCount > 1 && itemPosition < totalCount - 1) {//要除去footer占有的一个位置 78 | if (parent.getAdapter() instanceof SectionedRecyclerViewAdapter) { 79 | if (((SectionedRecyclerViewAdapter) parent.getAdapter()).isSectionHeaderPosition 80 | (itemPosition)) { 81 | return true; 82 | } 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | @Override 89 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 90 | super.onDraw(c, parent, state); 91 | drawHorizontal(c, parent); 92 | } 93 | 94 | /** 95 | * 绘制水平的分割线 96 | * @param c 97 | * @param parent 98 | */ 99 | private void drawHorizontal(Canvas c, RecyclerView parent) { 100 | 101 | int totalCount = parent.getAdapter().getItemCount(); 102 | 103 | //获取当前可见的item的数量,半个也算 104 | int childCount = parent.getChildCount(); 105 | for (int i = 0; i < childCount; i++) { 106 | //获取当前可见的view 107 | View child = parent.getChildAt(i); 108 | if (isDraw(parent, child, totalCount)) { 109 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); 110 | int left = child.getLeft() - params.leftMargin;//组件在容器X轴上的起点,需要注意,如果用户设置了left方向的Margin值,需要在取得itemViewleft属性后,将该margin抵消掉,因为,用户设置margin的意图明显不是想让分割线覆盖掉的 111 | int right = child.getRight() + params.rightMargin ; 112 | int top = child.getTop() - mDividerHeight - params.bottomMargin;//组件在容器Y轴上的起点 113 | int bottom = top + mDividerHeight; 114 | if (mDividerDrawable != null) { 115 | mDividerDrawable.setBounds(left, top, right, bottom); 116 | mDividerDrawable.draw(c); 117 | } 118 | if (mDividerPaint != null) { 119 | c.drawRect(left, top, right, bottom, mDividerPaint); 120 | } 121 | } 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_section_body.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_section_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_section_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 26 | 27 | 33 | 34 | 35 | 36 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/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 | 8 | #FAFAFA 9 | #AAAAAA 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12sp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SectionRecyclerViewAdapter 3 | 浏览 %1$s 人 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/easyandroid/sectionadapter/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.easyandroid.sectionadapter; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.2' 9 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 05 22:03:04 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pictures/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/1.png -------------------------------------------------------------------------------- /pictures/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/2.png -------------------------------------------------------------------------------- /pictures/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/3.png -------------------------------------------------------------------------------- /pictures/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/4.png -------------------------------------------------------------------------------- /pictures/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/5.png -------------------------------------------------------------------------------- /pictures/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/6.png -------------------------------------------------------------------------------- /pictures/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gycold/SectionRecyclerViewAdapter/ec627c4302b5d5f13656731c02ab30e162ad7f48/pictures/7.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------