├── .gitignore ├── PinnedSectionRecyclerView.iml ├── README.md ├── build.gradle ├── example.gif ├── example ├── build.gradle ├── example.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vk │ │ └── pinnedsectionrecyclerview │ │ └── example │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vk │ │ │ └── pinnedsectionrecyclerview │ │ │ └── example │ │ │ └── ExampleActivity.java │ └── res │ │ ├── layout │ │ ├── activity_example.xml │ │ ├── activity_gridview.xml │ │ └── item.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── vk │ └── pinnedsectionrecyclerview │ └── example │ └── ExampleUnitTest.java ├── example_gridview.png ├── example_listview.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── library.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vk │ │ └── pinnedsectionrecyclerview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vk │ │ │ └── pinnedsectionrecyclerview │ │ │ └── PinnedSectionRecyclerView.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── vk │ └── pinnedsectionrecyclerview │ └── ExampleUnitTest.java ├── local.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle 2 | example/example.iml 3 | library/library.iml 4 | .idea 5 | build 6 | example/build 7 | library/build -------------------------------------------------------------------------------- /PinnedSectionRecyclerView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PinnedSectionRecyclerView 2 | Simple RecyclerView with pinned sections for Android. 3 | 4 | # ScreenShot 5 | ![ListViewStyle](example_listview.png) 6 | ![ListViewStyle](example_gridview.png)  7 | 8 | # Gif 9 | ![ListViewStyle](example.gif) 10 | 11 | # Features 12 | 1. Support ListView and GridView style. 13 | 2. Support Section click and long click 14 | 15 | # Contact 16 | QQ:531372655 17 | 18 | Email:531372655@qq.com 19 | 20 | QQ学习群:476288569 21 | 22 | License 23 | ------- 24 | 25 | Copyright 2017 VK Tan 26 | 27 | Licensed under the Apache License, Version 2.0 (the "License"); 28 | you may not use this file except in compliance with the License. 29 | You may obtain a copy of the License at 30 | 31 | http://www.apache.org/licenses/LICENSE-2.0 32 | 33 | Unless required by applicable law or agreed to in writing, software 34 | distributed under the License is distributed on an "AS IS" BASIS, 35 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36 | See the License for the specific language governing permissions and 37 | limitations under the License. 38 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VK2012/PinnedSectionRecyclerView/dd252c6735ead21f53f7b3168950c271b2bbc27a/example.gif -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | defaultConfig { 7 | applicationId "com.vk.pinnedsectionrecyclerview.example" 8 | minSdkVersion 19 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile project(':library') 28 | compile 'com.android.support:appcompat-v7:25.1.0' 29 | compile 'com.android.support:design:25.1.0' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /example/example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /example/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 F:\VK\work_space\android_space\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /example/src/androidTest/java/com/vk/pinnedsectionrecyclerview/example/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vk.pinnedsectionrecyclerview.example; 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.vk.pinnedsectionrecyclerview.example", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/src/main/java/com/vk/pinnedsectionrecyclerview/example/ExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.vk.pinnedsectionrecyclerview.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.GridLayoutManager; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.LinearLayout; 15 | import android.widget.TextView; 16 | import android.widget.Toast; 17 | 18 | import com.vk.pinnedsectionrecyclerview.PinnedSectionRecyclerView; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Locale; 23 | 24 | public class ExampleActivity extends AppCompatActivity { 25 | 26 | public static final String TAG = "ExampleActivity"; 27 | 28 | private static final int[] COLORS = new int[] { 29 | R.color.green_light, R.color.orange_light, 30 | R.color.blue_light, R.color.red_light }; 31 | 32 | private List mList = new ArrayList<>(); 33 | 34 | MyAdapter myAdapter; 35 | 36 | PinnedSectionRecyclerView mPinnedRecyclerView; 37 | 38 | private static final int V_LISTVIEW = 0; 39 | 40 | private static final int V_GRIDVIEW = 2; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_example); 46 | 47 | mPinnedRecyclerView = (PinnedSectionRecyclerView)findViewById(R.id.recyclerView); 48 | 49 | mPinnedRecyclerView.setOnPinnedSectionTouchListener(mOnPinnedSectionTouchListener); 50 | 51 | updateLayoutManager(V_LISTVIEW); 52 | 53 | genData('A','Z'); 54 | 55 | myAdapter = new MyAdapter(); 56 | 57 | mPinnedRecyclerView.setAdapter(myAdapter); 58 | 59 | } 60 | 61 | private void updateLayoutManager(int mode){ 62 | switch (mode){ 63 | //-----listview 64 | case V_LISTVIEW: 65 | mPinnedRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayout.VERTICAL,false)); 66 | break; 67 | 68 | //--------grid view 69 | case V_GRIDVIEW: 70 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false); 71 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 72 | @Override 73 | public int getSpanSize(int position) { 74 | if( mList.get(position).type == Item.SECTION) 75 | return 3; 76 | else return 1; 77 | } 78 | }); 79 | mPinnedRecyclerView.setLayoutManager(gridLayoutManager); 80 | break; 81 | } 82 | 83 | } 84 | 85 | @Override 86 | public boolean onOptionsItemSelected(MenuItem item) { 87 | // Handle action bar item clicks here. The action bar will 88 | // automatically handle clicks on the Home/Up button, so long 89 | // as you specify a parent activity in AndroidManifest.xml. 90 | int id = item.getItemId(); 91 | 92 | //noinspection SimplifiableIfStatement 93 | boolean ret = false; 94 | switch (id){ 95 | case R.id.action_vertical_listview: 96 | updateLayoutManager(V_LISTVIEW); 97 | ret = true; 98 | break; 99 | case R.id.action_vertical_gridview: 100 | updateLayoutManager(V_GRIDVIEW); 101 | ret = true; 102 | break; 103 | } 104 | 105 | if(ret) 106 | return true; 107 | return super.onOptionsItemSelected(item); 108 | } 109 | 110 | @Override 111 | public boolean onCreateOptionsMenu(Menu menu) { 112 | getMenuInflater().inflate(R.menu.menu_main,menu); 113 | return true; 114 | } 115 | 116 | private void genData(char from, char to){ 117 | 118 | mList.clear(); 119 | final int sectionsNumber = to - from + 1; 120 | 121 | int sectionPosition = 0, listPosition = 0; 122 | int preSectionPosition = -1; 123 | for (char i=0; i -1){ 128 | Item preSection = mList.get(preSectionPosition); 129 | preSection.nextSectionPosition = sectionPosition; 130 | } 131 | mList.add(section); 132 | 133 | final int itemsNumber = (int) Math.abs((Math.cos(2f*Math.PI/3f * sectionsNumber / (i+1f)) * 25f)); 134 | for (int j=0;j implements PinnedSectionRecyclerView.Adapter{ 189 | 190 | @Override 191 | public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 192 | View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,parent,false); 193 | MyAdapter.MyViewHolder myViewHolder = new MyAdapter.MyViewHolder(itemView,mItemOnClickListener,mItemOnLongClickListener); 194 | return myViewHolder; 195 | } 196 | 197 | @Override 198 | public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) { 199 | Item item = mList.get(position); 200 | 201 | if (item.type == Item.SECTION) { 202 | //view.setOnClickListener(PinnedSectionListActivity.this); 203 | int color = holder.itemView.getContext().getResources().getColor(COLORS[item.sectionPosition % COLORS.length]); 204 | holder.mTextView.setBackgroundColor(color); 205 | } 206 | holder.mTextView.setText(mList.get(position).text); 207 | holder.itemView.setTag(position); 208 | } 209 | 210 | @Override 211 | public int getItemCount() { 212 | return mList.size(); 213 | } 214 | 215 | @Override 216 | public int getItemViewType(int position) { 217 | return mList.get(position).type; 218 | } 219 | 220 | @Override 221 | public boolean isPinnedSectionItem(int position) { 222 | return getItemViewType(position) == Item.SECTION; 223 | } 224 | 225 | @Override 226 | public int findSectionPosition(int position) { 227 | return mList.get(position).sectionPosition; 228 | } 229 | 230 | @Override 231 | public int findNextSectionPosition(int position) { 232 | Log.d(TAG, "findNextSectionPosition: "+position+" , "+mList.get(position).toString()); 233 | return mList.get(position).nextSectionPosition; 234 | } 235 | 236 | class MyViewHolder extends RecyclerView.ViewHolder{ 237 | 238 | TextView mTextView; 239 | public MyViewHolder(View itemView,View.OnClickListener itemOnClickListener,View.OnLongClickListener itemOnLongClickListener) { 240 | super(itemView); 241 | mTextView = (TextView) itemView.findViewById(R.id.tv); 242 | itemView.setOnClickListener(itemOnClickListener); 243 | itemView.setOnLongClickListener(itemOnLongClickListener); 244 | } 245 | 246 | } 247 | } 248 | 249 | static class Item { 250 | 251 | public static final int ITEM = 0; 252 | public static final int SECTION = 1; 253 | 254 | public final int type; 255 | public final String text; 256 | 257 | public int sectionPosition; 258 | public int nextSectionPosition = -1; 259 | public int listPosition; 260 | 261 | public Item(int type, String text) { 262 | this.type = type; 263 | this.text = text; 264 | } 265 | 266 | @Override 267 | public String toString() { 268 | return "Item{" + 269 | "type=" + type + 270 | ", text='" + text + '\'' + 271 | ", sectionPosition=" + sectionPosition + 272 | ", nextSectionPosition=" + nextSectionPosition + 273 | ", listPosition=" + listPosition + 274 | '}'; 275 | } 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_gridview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |