├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── aashreys │ └── sectioner │ ├── MultiItemSection.java │ ├── Section.java │ ├── SectionManager.java │ ├── SectionedRecyclerViewAdapter.java │ └── SingleItemSection.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── aashreys │ │ └── sectioner │ │ └── sample │ │ ├── MainActivity.java │ │ ├── sections │ │ ├── AlbumsSection.java │ │ ├── FooterSection.java │ │ ├── HeaderSection.java │ │ ├── SeparatorSection.java │ │ └── SongsSection.java │ │ ├── viewholders │ │ ├── AlbumViewHolder.java │ │ ├── FooterViewHolder.java │ │ ├── HeaderViewHolder.java │ │ ├── SeparatorViewHolder.java │ │ └── SongViewHolder.java │ │ └── views │ │ └── ItemDescriptionView.java │ └── res │ ├── drawable-hdpi │ ├── ic_album.png │ ├── ic_audiotrack.png │ └── ic_play_arrow.png │ ├── drawable-mdpi │ ├── ic_album.png │ ├── ic_audiotrack.png │ └── ic_play_arrow.png │ ├── drawable-xhdpi │ ├── ic_album.png │ ├── ic_audiotrack.png │ └── ic_play_arrow.png │ ├── drawable-xxhdpi │ ├── ic_album.png │ ├── ic_audiotrack.png │ └── ic_play_arrow.png │ ├── drawable-xxxhdpi │ ├── ic_album.png │ ├── ic_audiotrack.png │ └── ic_play_arrow.png │ ├── layout │ ├── activity_main.xml │ ├── custom_item_description.xml │ ├── list_album.xml │ ├── list_footer.xml │ ├── list_header.xml │ ├── list_separator.xml │ └── list_song.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-w820dp │ └── dimens.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/android,windows,macos,linux,intellij 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Intellij 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/libraries 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | ### Android Patch ### 48 | gen-external-apklibs 49 | 50 | 51 | ### Windows ### 52 | # Windows image file caches 53 | Thumbs.db 54 | ehthumbs.db 55 | 56 | # Folder config file 57 | Desktop.ini 58 | 59 | # Recycle Bin used on file shares 60 | $RECYCLE.BIN/ 61 | 62 | # Windows Installer files 63 | *.cab 64 | *.msi 65 | *.msm 66 | *.msp 67 | 68 | # Windows shortcuts 69 | *.lnk 70 | 71 | 72 | ### macOS ### 73 | *.DS_Store 74 | .AppleDouble 75 | .LSOverride 76 | 77 | # Icon must end with two \r 78 | Icon 79 | 80 | 81 | # Thumbnails 82 | ._* 83 | 84 | # Files that might appear in the root of a volume 85 | .DocumentRevisions-V100 86 | .fseventsd 87 | .Spotlight-V100 88 | .TemporaryItems 89 | .Trashes 90 | .VolumeIcon.icns 91 | .com.apple.timemachine.donotpresent 92 | 93 | # Directories potentially created on remote AFP share 94 | .AppleDB 95 | .AppleDesktop 96 | Network Trash Folder 97 | Temporary Items 98 | .apdisk 99 | 100 | 101 | ### Linux ### 102 | *~ 103 | 104 | # temporary files which can be created if a process still has a handle open of a deleted file 105 | .fuse_hidden* 106 | 107 | # KDE directory preferences 108 | .directory 109 | 110 | # Linux trash folder which might appear on any partition or disk 111 | .Trash-* 112 | 113 | 114 | ### Intellij ### 115 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 116 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 117 | 118 | # User-specific stuff: 119 | .idea/workspace.xml 120 | .idea/tasks.xml 121 | .idea/dictionaries 122 | .idea/vcs.xml 123 | .idea/jsLibraryMappings.xml 124 | 125 | # Sensitive or high-churn files: 126 | .idea/dataSources.ids 127 | .idea/dataSources.xml 128 | .idea/dataSources.local.xml 129 | .idea/sqlDataSources.xml 130 | .idea/dynamic.xml 131 | .idea/uiDesigner.xml 132 | 133 | # Gradle: 134 | .idea/gradle.xml 135 | .idea/libraries 136 | 137 | # Mongo Explorer plugin: 138 | .idea/mongoSettings.xml 139 | 140 | ## File-based project format: 141 | *.iws 142 | 143 | ## Plugin-specific files: 144 | 145 | # IntelliJ 146 | /out/ 147 | 148 | # mpeltonen/sbt-idea plugin 149 | .idea_modules/ 150 | 151 | # JIRA plugin 152 | atlassian-ide-plugin.xml 153 | 154 | # Crashlytics plugin (for Android Studio and IntelliJ) 155 | com_crashlytics_export_strings.xml 156 | crashlytics.properties 157 | crashlytics-build.properties 158 | fabric.properties 159 | 160 | ### Intellij Patch ### 161 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 162 | 163 | # *.iml 164 | # modules.xml 165 | # .idea/misc.xml 166 | # *.ipr -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/android,windows,macos,linux,intellij 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Intellij 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/libraries 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | ### Android Patch ### 48 | gen-external-apklibs 49 | 50 | 51 | ### Windows ### 52 | # Windows image file caches 53 | Thumbs.db 54 | ehthumbs.db 55 | 56 | # Folder config file 57 | Desktop.ini 58 | 59 | # Recycle Bin used on file shares 60 | $RECYCLE.BIN/ 61 | 62 | # Windows Installer files 63 | *.cab 64 | *.msi 65 | *.msm 66 | *.msp 67 | 68 | # Windows shortcuts 69 | *.lnk 70 | 71 | 72 | ### macOS ### 73 | *.DS_Store 74 | .AppleDouble 75 | .LSOverride 76 | 77 | # Icon must end with two \r 78 | Icon 79 | 80 | 81 | # Thumbnails 82 | ._* 83 | 84 | # Files that might appear in the root of a volume 85 | .DocumentRevisions-V100 86 | .fseventsd 87 | .Spotlight-V100 88 | .TemporaryItems 89 | .Trashes 90 | .VolumeIcon.icns 91 | .com.apple.timemachine.donotpresent 92 | 93 | # Directories potentially created on remote AFP share 94 | .AppleDB 95 | .AppleDesktop 96 | Network Trash Folder 97 | Temporary Items 98 | .apdisk 99 | 100 | 101 | ### Linux ### 102 | *~ 103 | 104 | # temporary files which can be created if a process still has a handle open of a deleted file 105 | .fuse_hidden* 106 | 107 | # KDE directory preferences 108 | .directory 109 | 110 | # Linux trash folder which might appear on any partition or disk 111 | .Trash-* 112 | 113 | 114 | ### Intellij ### 115 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 116 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 117 | 118 | # User-specific stuff: 119 | .idea/workspace.xml 120 | .idea/tasks.xml 121 | .idea/dictionaries 122 | .idea/vcs.xml 123 | .idea/jsLibraryMappings.xml 124 | 125 | # Sensitive or high-churn files: 126 | .idea/dataSources.ids 127 | .idea/dataSources.xml 128 | .idea/dataSources.local.xml 129 | .idea/sqlDataSources.xml 130 | .idea/dynamic.xml 131 | .idea/uiDesigner.xml 132 | 133 | # Gradle: 134 | .idea/gradle.xml 135 | .idea/libraries 136 | 137 | # Mongo Explorer plugin: 138 | .idea/mongoSettings.xml 139 | 140 | ## File-based project format: 141 | *.iws 142 | 143 | ## Plugin-specific files: 144 | 145 | # IntelliJ 146 | /out/ 147 | 148 | # mpeltonen/sbt-idea plugin 149 | .idea_modules/ 150 | 151 | # JIRA plugin 152 | atlassian-ide-plugin.xml 153 | 154 | # Crashlytics plugin (for Android Studio and IntelliJ) 155 | com_crashlytics_export_strings.xml 156 | crashlytics.properties 157 | crashlytics-build.properties 158 | fabric.properties 159 | 160 | ### Intellij Patch ### 161 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 162 | 163 | # *.iml 164 | # modules.xml 165 | # .idea/misc.xml 166 | # *.ipr 167 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Sectioner -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | 25 | 37 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 1.7 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Aashrey Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sectioner 2 | An Android RecyclerView.Adapter management library that makes creating and editing heterogenous view based lists easy. 3 | 4 | ## Introduction 5 | Setting up a RecyclerView and its adapter in Android is pretty straightforward UNTIL you hit the heterogenous view based lists. Then its just a complex often ugly mathematical mapping of position to view that developers have to create manually for each type of view their list displays. Its grunt work and it slows you down and as a developer you shouldn't have to deal with it. 6 | 7 | What Sectioner does is generalize the aforementioned mapping so that you can focus on what's important and let Sectioner handle changes to your RecyclerView's data. Sectioner offers a consistent API for creating and editing heterogenous view lists in RecyclerViews. Behind the scenes Sectioner constantly keeps track of item and view positions and automatically detects whether an item has been added, edited or removed and animates the changes in your RecyclerView. 8 | 9 | ## Core Classes 10 | 1. Section - A Section is a grouping of data items which are represented by a single view type. With Sectioner you can add multiple Sections to your RecyclerView, each representing a different view type. Sections can contain a single item (for headers/footers or separators) or multiple items (for traditional list items). You can also extend and override a Section's default behavior and it is encouraged that you do so. 11 | 2. SectionManager - The SectionManager is what keeps track of Sections and relays the information to the SectionedRecyclerViewAdapter so that they are rendered on screen. Any change to the underlying Sections with a SectionManager immediately trigger a position mapping update following which the adapter is updated. 12 | 3. SectionedRecyclerViewAdapter - This is an extension of the base adapter class for interfacing with a SectionManager. 13 | 14 | These clases have been extensively JavaDocd. It is recommended that you review the source code to better understand how these classes function. 15 | 16 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Sectioner-green.svg?style=true)](https://android-arsenal.com/details/1/4079) 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 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.2.3' 10 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Aug 16 19:39:18 IST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.android.support:recyclerview-v7:25.0.1' 24 | } 25 | -------------------------------------------------------------------------------- /library/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 /Users/aashreys/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/java/com/aashreys/sectioner/MultiItemSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * An implementation of {@link Section} designed to containing multiple data {@link Data}s 12 | * corresponding to a single View Type. 13 | *

14 | * Created by aashreys on 20/03/16. 15 | */ 16 | public abstract class MultiItemSection extends 17 | Section { 18 | 19 | private final Object writeLock = new Object(); 20 | 21 | @NonNull protected final List dataList; 22 | 23 | /** 24 | * Creates an empty {@link MultiItemSection}. 25 | */ 26 | public MultiItemSection() { 27 | super(); 28 | this.dataList = new ArrayList<>(); 29 | } 30 | 31 | /** 32 | * Creates a new {@link MultiItemSection} with added {@link Data}s. This does not trigger an 33 | * animated addition in the {@link RecyclerView}. 34 | * 35 | * @param dataList - {@link Data}s to add. 36 | */ 37 | public MultiItemSection(@NonNull List dataList) { 38 | super(); 39 | this.dataList = dataList; 40 | } 41 | 42 | @Override 43 | public void add(@NonNull Data... datas) { 44 | int oldSize; 45 | synchronized (writeLock) { 46 | oldSize = size(); 47 | Collections.addAll(dataList, datas); 48 | updatePositionMapping(); 49 | } 50 | _notifyItemRangeInserted(oldSize, datas.length); 51 | } 52 | 53 | @Override 54 | public void add(int itemPosition, @NonNull Data data) { 55 | synchronized (writeLock) { 56 | dataList.add(itemPosition, data); 57 | updatePositionMapping(); 58 | } 59 | _notifyItemAdded(itemPosition); 60 | } 61 | 62 | @Override 63 | public void remove(@NonNull Data data) { 64 | int itemPosition; 65 | synchronized (writeLock) { 66 | itemPosition = dataList.indexOf(data); 67 | dataList.remove(data); 68 | updatePositionMapping(); 69 | } 70 | _notifyItemRemoved(itemPosition); 71 | } 72 | 73 | @Override 74 | public void remove(int itemPosition) { 75 | synchronized (writeLock) { 76 | dataList.remove(itemPosition); 77 | updatePositionMapping(); 78 | } 79 | _notifyItemRemoved(itemPosition); 80 | } 81 | 82 | @Override 83 | public void replace(@NonNull Data data, boolean notifyAdapter) { 84 | int itemPosition; 85 | synchronized (writeLock) { 86 | itemPosition = dataList.indexOf(data); 87 | dataList.remove(data); 88 | dataList.add(itemPosition, data); 89 | } 90 | if (notifyAdapter) { 91 | _notifyItemReplaced(itemPosition); 92 | } 93 | } 94 | 95 | @Override 96 | public void replace(int itemPosition, @NonNull Data data, boolean notifyAdapter) { 97 | synchronized (writeLock) { 98 | dataList.remove(itemPosition); 99 | dataList.add(itemPosition, data); 100 | } 101 | if (notifyAdapter) { 102 | _notifyItemReplaced(itemPosition); 103 | } 104 | } 105 | 106 | @Override 107 | public void clearAndAdd(Data... datas) { 108 | int oldSize, newSize; 109 | synchronized (writeLock) { 110 | oldSize = dataList.size(); 111 | dataList.clear(); 112 | Collections.addAll(dataList, datas); 113 | updatePositionMapping(); 114 | newSize = dataList.size(); 115 | } 116 | if (oldSize > newSize) { 117 | _notifyItemRangeChanged(0, newSize); 118 | _notifyItemRangeRemoved(newSize, oldSize - newSize); 119 | } else if (newSize > oldSize) { 120 | _notifyItemRangeChanged(0, oldSize); 121 | _notifyItemRangeInserted(oldSize, newSize - oldSize); 122 | } else { 123 | _notifyItemRangeChanged(0, oldSize); 124 | } 125 | } 126 | 127 | @Override 128 | public boolean contains(Data data) { 129 | return data != null && dataList.contains(data); 130 | } 131 | 132 | @Override 133 | public void clear() { 134 | int oldSize; 135 | synchronized (writeLock) { 136 | oldSize = dataList.size(); 137 | dataList.clear(); 138 | updatePositionMapping(); 139 | } 140 | _notifyItemRangeRemoved(0, oldSize); 141 | } 142 | 143 | @Override 144 | public int firstIndexOf(@NonNull Data data) { 145 | return dataList.indexOf(data); 146 | } 147 | 148 | @Override 149 | public int lastIndexOf(@NonNull Data data) { 150 | return dataList.lastIndexOf(data); 151 | } 152 | 153 | @Override 154 | public int size() { 155 | return dataList.size(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /library/src/main/java/com/aashreys/sectioner/Section.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | /** 10 | * A Section represents a collection of data items - {@link Data} - which correspond to a single 11 | * View Type in the RecyclerView Adapter. 12 | * This class provides APIs for manipulating individual items in a {@link Section}, useful for 13 | * single view level control over a {@link RecyclerView}'s children. 14 | * 15 | * @author aashreys on 23/03/16. 16 | */ 17 | public abstract class Section { 18 | 19 | @Nullable private SectionManager manager; 20 | 21 | private boolean isEnabled = true; 22 | 23 | public Section() {} 24 | 25 | void setManager(@Nullable SectionManager manager) { 26 | this.manager = manager; 27 | } 28 | 29 | /** 30 | * Creates and returns a {@link ViewHolder} for binding your {@link View} to an {@link Data} 31 | * from this Section. You can inflate your {@link View} and pass it to the {@link ViewHolder} 32 | * in this method. 33 | * 34 | * @param parent - the parent RecyclerView. 35 | * @return {@link ViewHolder} corresponding to this {@link Section}'s {@link View}. 36 | */ 37 | protected abstract ViewHolder createViewHolder(ViewGroup parent); 38 | 39 | /** 40 | * Binds the {@link View} associated with this {@link Section}. 41 | * 42 | * @param holder the {@link ViewHolder} created in {@link #createViewHolder(ViewGroup)} 43 | * @param sectionPosition the position of {@link ViewHolder} in this section. 44 | * @param adapterPosition the position of this {@link ViewHolder} in the adapter. 45 | */ 46 | protected abstract void bindViewHolder( 47 | ViewHolder holder, 48 | int sectionPosition, 49 | int adapterPosition 50 | ); 51 | 52 | /** 53 | * Adds data {@link Data}s to the end of this {@link Section} and notifies the adapter. 54 | * 55 | * @param datas {@link Data}s to add. 56 | * @see MultiItemSection#add(Object[]) 57 | * @see SingleItemSection#add(Object[]) 58 | */ 59 | public abstract void add(@NonNull Data... datas); 60 | 61 | /** 62 | * Adds a single {@link Data} to a specific position in this {@link Section} and notifies the 63 | * adapter. 64 | * 65 | * @param position position to add the {@link Data} to. 66 | * @param data {@link Data} to add. 67 | * @throws IndexOutOfBoundsException if {@param position} is not found or invalid. 68 | */ 69 | public abstract void add(int position, @NonNull Data data); 70 | 71 | /** 72 | * Removes the first occurrence of a single {@link Data} in this {@link Section} and notifies 73 | * the adapter. 74 | * 75 | * @param data {@link Data} to remove 76 | */ 77 | public abstract void remove(@NonNull Data data); 78 | 79 | /** 80 | * Removes the {@link Data} at a specified position. 81 | * 82 | * @param position position to remove {@link Data} from 83 | * @throws IndexOutOfBoundsException if {@param position} is not found or invalid. 84 | */ 85 | public abstract void remove(int position); 86 | 87 | /** 88 | * Replaces the first occurrence of an {@link Data} with the new {@link Data} in this {@link 89 | * Section} and optionally notifies the adapter. The optional notification can be used 90 | * to either immediately notify the adapter or allow the user to manually reflect the update 91 | * at a later time or in response to an event. 92 | * 93 | * @param data {@link Data} to replace with an updated instance 94 | * @param notifyAdapter controls whether or not the adapter is notified to the replacement. 95 | */ 96 | public abstract void replace(@NonNull Data data, boolean notifyAdapter); 97 | 98 | /** 99 | * Similar to {@link #replace(Object, boolean)}, except that the replacement is made at the 100 | * specified position. 101 | * 102 | * @param position position to remove item at 103 | * @param data {@link Data} to replace the removed item with 104 | * @param notifyAdapter controls whether or not the adapter is notified to the replacement. 105 | */ 106 | public abstract void replace(int position, @NonNull Data data, boolean notifyAdapter); 107 | 108 | /** 109 | * Clears this {@link Section} adds {@link Data}s to the {@link Section}. The adapter is only 110 | * notified once, at the end of the replacement operation and the notification is that of the 111 | * difference in the size of the list, not the entire removal and re-addition of items, hence 112 | * making it a more efficient notification method. 113 | *

114 | * It is recommended to use this method instead of calling {@link #clear} and 115 | * {@link #add(Object[])} in succession. 116 | * 117 | * @param datas {@link Data}s to replace the current {@link Data}s with. 118 | */ 119 | public abstract void clearAndAdd(Data... datas); 120 | 121 | /** 122 | * Checks if an {@link Data} is contained in this {@link Section}. 123 | * 124 | * @param data {@link Data} to check for 125 | * @return True if the {@link Data} is contained, False otherwise. 126 | */ 127 | public abstract boolean contains(Data data); 128 | 129 | /** 130 | * Removes all {@link Data}s from this {@link Section}. 131 | */ 132 | public abstract void clear(); 133 | 134 | /** 135 | * Returns the first position of an {@link Data} in this {@link Section}. 136 | * 137 | * @param data {@link Data} to find position for. 138 | * @return First position of the item if it is found in this Section, -1 otherwise. 139 | */ 140 | public abstract int firstIndexOf(@NonNull Data data); 141 | 142 | /** 143 | * Returns the last position of an {@link Data} in this {@link Section}. 144 | * 145 | * @param data {@link Data} to find position for. 146 | * @return Last position of the item if it is found in this section, -1 otherwise. 147 | */ 148 | public abstract int lastIndexOf(@NonNull Data data); 149 | 150 | /** 151 | * Convenience method to check if this {@link Section} is empty. 152 | */ 153 | public boolean isEmpty() { 154 | return size() == 0; 155 | } 156 | 157 | /** 158 | * Returns the number of {@link Data}s present in this {@link Section}. 159 | */ 160 | public abstract int size(); 161 | 162 | /** 163 | * Getter 164 | * 165 | * @return True if this {@link Section} is enabled, false otherwise. 166 | */ 167 | public boolean isEnabled() { 168 | return this.isEnabled; 169 | } 170 | 171 | /** 172 | * Enables/disables this {@link Section}. When enabled this {@link Section} will appear in a 173 | * {@link RecyclerView} list. Upon disabling this {@link Section} will disappear from the list. 174 | * This is a convenience method so that developers can hide {@link Section}s without having to 175 | * mess around with the internal {@link Data} list. 176 | */ 177 | public void setEnabled(boolean isEnabled) { 178 | if (this.isEnabled != isEnabled) { 179 | this.isEnabled = isEnabled; 180 | updatePositionMapping(); 181 | if (isEnabled) { 182 | _notifyItemRangeInserted(0, size()); 183 | } else { 184 | _notifyItemRangeRemoved(0, size()); 185 | } 186 | } 187 | 188 | } 189 | 190 | /** 191 | * Helper method to invoke {@link SectionManager#createItemSectionMappings()} on the {@link 192 | * SectionManager} this {@link Section} is associated with. 193 | */ 194 | protected void updatePositionMapping() { 195 | if (manager != null) { 196 | this.manager.createItemSectionMappings(); 197 | } 198 | } 199 | 200 | /** 201 | * Helper method to notify the adapter for this {@link Section} of the addition of multiple new 202 | * {@link Data}s to this {@link Section}. 203 | * 204 | * @param sectionStartPosition section position for the first item that was inserted 205 | * @param itemCount number of items that were inserted 206 | */ 207 | protected void _notifyItemRangeInserted(int sectionStartPosition, int itemCount) { 208 | if (manager != null) { 209 | manager.getAdapter().notifyItemRangeInserted( 210 | manager.getFirstItemAdapterPositionForSection(this) + sectionStartPosition, 211 | itemCount 212 | ); 213 | } 214 | } 215 | 216 | /** 217 | * Helper method to notify the adapter for this {@link Section} of the removal of multiple new 218 | * {@link Data}s to this {@link Section}. 219 | * 220 | * @param sectionStartPosition previous section position of the first item that was removed 221 | * @param itemCount number of items that were removed 222 | */ 223 | protected void _notifyItemRangeRemoved(int sectionStartPosition, int itemCount) { 224 | if (manager != null) { 225 | manager.getAdapter().notifyItemRangeRemoved( 226 | manager.getFirstItemAdapterPositionForSection(this) + sectionStartPosition, 227 | itemCount 228 | ); 229 | } 230 | } 231 | 232 | /** 233 | * Helper method to notify the adapter for this {@link Section} that a range of {@link Data}s 234 | * has been changed. 235 | * 236 | * @param sectionStartPosition section position of the first item that has changed 237 | * @param itemCount number of items that have changed 238 | */ 239 | protected void _notifyItemRangeChanged(int sectionStartPosition, int itemCount) { 240 | if (manager != null) { 241 | manager.getAdapter().notifyItemRangeChanged( 242 | manager.getFirstItemAdapterPositionForSection(this) + sectionStartPosition, 243 | itemCount 244 | ); 245 | } 246 | } 247 | 248 | /** 249 | * Helper method to notify the adapter for this {@link Section} that an {@link Data} has been 250 | * added. 251 | * 252 | * @param itemPosition Position at which the {@link Data} was added. 253 | */ 254 | protected void _notifyItemAdded(int itemPosition) { 255 | if (manager != null) { 256 | manager.getAdapter().notifyItemInserted( 257 | manager.getFirstItemAdapterPositionForSection(this) + itemPosition 258 | ); 259 | } 260 | } 261 | 262 | /** 263 | * Helper method to notify the adapter for this {@link Section} that an {@link Data} has been 264 | * removed. 265 | * 266 | * @param itemPosition Position from which the {@link Data} was removed. 267 | */ 268 | protected void _notifyItemRemoved(int itemPosition) { 269 | if (manager != null) { 270 | manager.getAdapter().notifyItemRemoved( 271 | manager.getFirstItemAdapterPositionForSection(this) + itemPosition 272 | ); 273 | } 274 | } 275 | 276 | /** 277 | * Helper method to notify the adapter for this {@link Section} that an {@link Data} has been 278 | * replaced. 279 | * 280 | * @param itemPosition Position at which the {@link Data} was replaced. 281 | */ 282 | protected void _notifyItemReplaced(int itemPosition) { 283 | if (manager != null) { 284 | manager.getAdapter().notifyItemChanged( 285 | manager.getFirstItemAdapterPositionForSection(this) + itemPosition 286 | ); 287 | } 288 | } 289 | 290 | } 291 | -------------------------------------------------------------------------------- /library/src/main/java/com/aashreys/sectioner/SectionManager.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.TreeMap; 11 | 12 | /** 13 | * Provides APIs for manipulating entire {@link Section}s added to the {@link RecyclerView} and 14 | * provides {@link Section} level control over the {@link RecyclerView}'s children. 15 | *

16 | * Created by aashreys on 20/03/16. 17 | */ 18 | public class SectionManager { 19 | 20 | private static final String TAG = SectionManager.class.getSimpleName(); 21 | 22 | private final Object writeLock = new Object(); 23 | 24 | /** 25 | * The {@link SectionedRecyclerViewAdapter} associated with this {@link SectionManager} 26 | */ 27 | @NonNull private SectionedRecyclerViewAdapter adapter; 28 | 29 | /** 30 | * Internal list of {@link Section}s which serves as data for this {@link SectionManager} 31 | */ 32 | @NonNull private List

sections; 33 | 34 | /** 35 | * Maps the first item position in a non-empty {@link Section} to its respective {@link 36 | * Section}. 37 | */ 38 | @NonNull private TreeMap itemPosToSectionPosMap; 39 | 40 | /** 41 | * Maps a non-empty {@link Section} to the position of its first item. 42 | */ 43 | @NonNull private TreeMap sectionPosToItemPosMap; 44 | 45 | /** 46 | * Represents the cumulative number of items contained in all {@link Section}s of this {@link 47 | * SectionManager}. Size does not include {@link Section}s which have been disabled via the 48 | * {@link Section#setEnabled(boolean)} API. 49 | */ 50 | private int itemsSize; 51 | 52 | /** 53 | * Creates a {@link SectionManager} and binds it to a {@link SectionedRecyclerViewAdapter}. 54 | * 55 | * @param adapter - {@link SectionedRecyclerViewAdapter} to bind to. 56 | */ 57 | public SectionManager(@NonNull SectionedRecyclerViewAdapter adapter) { 58 | this.sections = new ArrayList<>(); 59 | this.adapter = adapter; 60 | this.itemPosToSectionPosMap = new TreeMap<>(); 61 | this.sectionPosToItemPosMap = new TreeMap<>(); 62 | } 63 | 64 | /** 65 | * Returns the {@link SectionedRecyclerViewAdapter associated with this {@link SectionManager}. 66 | */ 67 | @NonNull 68 | public SectionedRecyclerViewAdapter getAdapter() { 69 | return adapter; 70 | } 71 | 72 | /** 73 | * Returns all {@link Section}s added to this {@link SectionManager}. 74 | */ 75 | @NonNull 76 | public List
getSections() { 77 | return sections; 78 | } 79 | 80 | /** 81 | * Returns a {@link Section} a position in this {@link SectionManager}. 82 | * 83 | * @param sectionPosition position of {@link Section} to return 84 | * @return {@link Section} at specified position 85 | * @throws IndexOutOfBoundsException 86 | */ 87 | public Section get(int sectionPosition) { 88 | return sections.get(sectionPosition); 89 | } 90 | 91 | /** 92 | * Adds {@link Section}s to the end of {@link #sections}, updates the internal mappings and 93 | * notifies the {@link #adapter}. 94 | * 95 | * @param sections {@link Section}s to add 96 | */ 97 | public void addAll(Section... sections) { 98 | int oldItemSize; 99 | synchronized (writeLock) { 100 | oldItemSize = itemsSize; 101 | for (Section section : sections) { 102 | section.setManager(this); 103 | this.sections.add(section); 104 | } 105 | createItemSectionMappings(); 106 | } 107 | adapter.notifyItemRangeInserted(oldItemSize, itemsSize - oldItemSize); 108 | } 109 | 110 | /** 111 | * Maps the position of the first item in a section to its respective {@link Section} and 112 | * vice-versa. Must be called after every change in {@link #sections} or its underlying {@link 113 | * Section}'s item list. 114 | * 115 | * @see #itemPosToSectionPosMap 116 | * @see #sectionPosToItemPosMap 117 | * @see #getSectionPositionForAdapterPosition(int) 118 | */ 119 | protected void createItemSectionMappings() { 120 | itemsSize = 0; 121 | itemPosToSectionPosMap = new TreeMap<>(); 122 | sectionPosToItemPosMap = new TreeMap<>(); 123 | for (int i = 0; i < sections.size(); i++) { 124 | if (sections.get(i).isEnabled() && sections.get(i).size() > 0) { 125 | itemPosToSectionPosMap.put(itemsSize, i); 126 | sectionPosToItemPosMap.put(i, itemsSize); 127 | itemsSize += sections.get(i).size(); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Adds a {@link Section} at a given position in {@link #sections}, updates the internal 134 | * mappings and notifies the {@link #adapter}. 135 | * 136 | * @param position position to add {@link Section} at. 137 | * @param section {@link Section} to add. 138 | */ 139 | public void add(int position, Section section) { 140 | synchronized (writeLock) { 141 | section.setManager(this); 142 | sections.add(position, section); 143 | createItemSectionMappings(); 144 | } 145 | adapter.notifyItemRangeInserted( 146 | getFirstItemAdapterPositionForSectionPosition(position), section.size() 147 | ); 148 | } 149 | 150 | /** 151 | * Gets the adapter position for the first item in a {@link Section} specified by a position in 152 | * {@link #sections}. 153 | * 154 | * @param sectionPosition position of the {@link Section} 155 | */ 156 | protected int getFirstItemAdapterPositionForSectionPosition(int sectionPosition) { 157 | if (sectionPosToItemPosMap.containsKey(sectionPosition)) { 158 | // This is a non-empty section. Return it's first position 159 | return sectionPosToItemPosMap.get(sectionPosition); 160 | } else { 161 | // This is an empty section, get the first item position from the map for a non-empty 162 | // section preceding this 163 | Map.Entry entry = sectionPosToItemPosMap.floorEntry(sectionPosition); 164 | return entry != null ? entry.getValue() : 0; 165 | } 166 | } 167 | 168 | /** 169 | * Gets the adapter position for the first item in a given {@link Section} present in 170 | * {@link #sections}. 171 | * 172 | * @param section {@link Section} for whose item the position returned 173 | */ 174 | protected int getFirstItemAdapterPositionForSection(Section section) { 175 | return getFirstItemAdapterPositionForSectionPosition(sections.indexOf(section)); 176 | } 177 | 178 | /** 179 | * Removes the {@link Section} at a given position from {@link #sections}, updates the internal 180 | * mappings and notifies the {@link #adapter}. 181 | * 182 | * @param position position to remove {@link Section} from. 183 | */ 184 | public void remove(int position) { 185 | remove(sections.get(position)); 186 | } 187 | 188 | /** 189 | * Removes the first occurrence of a {@link Section} from {@link #sections}, updates the 190 | * internal mappings and notifies the {@link #adapter}. 191 | * 192 | * @param section {@link Section} to remove. 193 | */ 194 | public void remove(Section section) { 195 | int positionStart; 196 | synchronized (writeLock) { 197 | positionStart 198 | = getFirstItemAdapterPositionForSectionPosition(sections.indexOf(section)); 199 | sections.remove(section); 200 | section.setManager(null); 201 | createItemSectionMappings(); 202 | } 203 | adapter.notifyItemRangeRemoved(positionStart, section.size()); 204 | } 205 | 206 | /** 207 | * Replaces the first occurrence of a {@link Section} in {@link #sections}, updates the 208 | * internal mappings and notifies the {@link #adapter}. 209 | * 210 | * @param section {@link Section} to replace with 211 | */ 212 | public void replace(Section section) { 213 | replace(sections.indexOf(section), section); 214 | } 215 | 216 | /** 217 | * Replaces a {@link Section} at the given position with a {@link Section}, updates the 218 | * internal mappings and notifies the {@link #adapter}. 219 | * 220 | * @param position position to replace {@link Section} at. 221 | * @param section new {@link Section} to replace with. 222 | */ 223 | public void replace(int position, Section section) { 224 | int newSectionItemCount, oldSectionItemCount; 225 | synchronized (writeLock) { 226 | newSectionItemCount = section.size(); 227 | oldSectionItemCount = sections.get(position).size(); 228 | sections.get(position).setManager(null); 229 | sections.remove(position); 230 | section.setManager(this); 231 | sections.add(position, section); 232 | createItemSectionMappings(); 233 | } 234 | int itemsDiff = newSectionItemCount - oldSectionItemCount; 235 | int sectionFirstItemPos = getFirstItemAdapterPositionForSectionPosition(position); 236 | if (itemsDiff > 0) { 237 | // Items have changed and have been added 238 | adapter.notifyItemRangeChanged(sectionFirstItemPos, oldSectionItemCount); 239 | adapter.notifyItemRangeInserted( 240 | sectionFirstItemPos + oldSectionItemCount, 241 | Math.abs(itemsDiff) 242 | ); 243 | } else if (itemsDiff < 0) { 244 | // Items have changed and have been removed 245 | adapter.notifyItemRangeChanged(sectionFirstItemPos, newSectionItemCount); 246 | adapter.notifyItemRangeRemoved( 247 | sectionFirstItemPos + newSectionItemCount, 248 | Math.abs(itemsDiff) 249 | ); 250 | } else { 251 | // Items have changed in place 252 | adapter.notifyItemRangeChanged(sectionFirstItemPos, newSectionItemCount); 253 | } 254 | } 255 | 256 | /** 257 | * Checks if a {@link Section} is contained in {@link #sections}. 258 | * 259 | * @param section {@link Section} to check for. 260 | * @return true if {@param section} is found. 261 | */ 262 | public boolean contains(Section section) { 263 | return section != null && sections.contains(section); 264 | } 265 | 266 | /** 267 | * Clears the sections list - {@link #sections}, thereby flushing all held {@link Section}s and 268 | * their items. Also updates the internal mappings and notifies the {@link #adapter}. 269 | */ 270 | public void clear() { 271 | synchronized (writeLock) { 272 | for (Section section : sections) { 273 | section.setManager(null); 274 | } 275 | this.sections = new ArrayList<>(); 276 | createItemSectionMappings(); 277 | } 278 | adapter.notifyDataSetChanged(); 279 | } 280 | 281 | /** 282 | * Gets the Section items total number of items contained in this {@link SectionManager}}. Size 283 | * does not items contained in disabled {@link Section}s. 284 | */ 285 | public int getItemCount() { 286 | return itemsSize; 287 | } 288 | 289 | /** 290 | * Gets the total number of {@link Section}s contained in this {@link SectionManager} 291 | */ 292 | public int getSectionCount() { 293 | return sections.size(); 294 | } 295 | 296 | /** 297 | * Returns the first position of a {@link Section} in {@link #sections} if it is found, else 298 | * returns -1. 299 | */ 300 | public int indexOf(Section section) { 301 | return sections.indexOf(section); 302 | } 303 | 304 | /** 305 | * Returns a {@link Section} for a given view type. For use with {@link 306 | * SectionedRecyclerViewAdapter} only. 307 | * 308 | * @param adapterViewType view type provided by {@link SectionedRecyclerViewAdapter} in {@link 309 | * SectionedRecyclerViewAdapter#onCreateViewHolder(ViewGroup, int)} 310 | * @return {@link Section} 311 | */ 312 | protected Section getSectionForAdapterViewType(int adapterViewType) { 313 | return sections.get(adapterViewType); 314 | } 315 | 316 | protected int getViewTypeForAdapterPosition(int adapterPosition) { 317 | return getSectionPositionForAdapterPosition(adapterPosition); 318 | } 319 | 320 | protected int getSectionPositionForAdapterPosition(int adapterPosition) { 321 | return itemPosToSectionPosMap.floorEntry(adapterPosition).getValue(); 322 | } 323 | 324 | protected int getItemSectionPosition(int adapterPosition) { 325 | return adapterPosition - 326 | getFirstItemAdapterPositionForSectionPosition(getSectionPositionForAdapterPosition( 327 | adapterPosition)); 328 | } 329 | 330 | protected Section getSectionForAdapterPosition(int adapterPosition) { 331 | return sections.get(getSectionPositionForAdapterPosition(adapterPosition)); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /library/src/main/java/com/aashreys/sectioner/SectionedRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | /** 8 | * A implementation of the {@link RecyclerView.Adapter} for interfacing with {@link SectionManager} 9 | * to display different {@link Section}s. Use {@link #getSectionManager()} to obtain a {@link 10 | * SectionManager} with which you can manipulate the {@link RecyclerView}'s underlying data list. 11 | *

12 | * Created by aashreys on 19/03/16. 13 | */ 14 | public class SectionedRecyclerViewAdapter extends RecyclerView.Adapter { 15 | 16 | private static final String TAG = SectionedRecyclerViewAdapter.class.getSimpleName(); 17 | 18 | @NonNull private SectionManager sectionManager; 19 | 20 | public SectionedRecyclerViewAdapter() { 21 | this.sectionManager = new SectionManager(this); 22 | } 23 | 24 | @NonNull 25 | public SectionManager getSectionManager() { 26 | return sectionManager; 27 | } 28 | 29 | /** 30 | * Delegates to the {@link #sectionManager} which fetches the appropriate {@link Section} for 31 | * the given ViewType and calls {@link Section#createViewHolder(ViewGroup)} to create an 32 | * appropriate {@link android.support.v7.widget.RecyclerView.ViewHolder} for the {@link 33 | * android.view.View} to be inflated. 34 | */ 35 | @Override 36 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | return sectionManager.getSectionForAdapterViewType(viewType).createViewHolder(parent); 38 | } 39 | 40 | /** 41 | * Delegates to the {@link #sectionManager} which fetches the appropriate {@link Section} for 42 | * the given Adapter Position and calls {@link Section#bindViewHolder(RecyclerView.ViewHolder, 43 | * int, int)} to bind the ViewHolder to the View corresponding to the Item specified by the 44 | * AdapterPosition. 45 | */ 46 | @Override 47 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int adapterPosition) { 48 | sectionManager.getSectionForAdapterPosition(adapterPosition) 49 | .bindViewHolder( 50 | holder, 51 | sectionManager.getItemSectionPosition(adapterPosition), 52 | adapterPosition 53 | ); 54 | } 55 | 56 | @Override 57 | public int getItemViewType(int position) { 58 | return sectionManager.getViewTypeForAdapterPosition(position); 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return sectionManager.getItemCount(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /library/src/main/java/com/aashreys/sectioner/SingleItemSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | 6 | /** 7 | * An implementation of {@link Section} designed to contain a single data {@link Data}. Useful for 8 | * creating list headers, footers and separators. 9 | *

10 | * Created by aashreys on 20/03/16. 11 | */ 12 | public abstract class SingleItemSection 13 | extends Section { 14 | 15 | private final Object writeLock = new Object(); 16 | 17 | @NonNull protected Data data; 18 | 19 | private static final String MULTI_ITEM_OPERATION_ERROR = "MultiItem operations are unsupported in SingleItemSection"; 20 | 21 | public SingleItemSection(Data data) { 22 | super(); 23 | this.data = data; 24 | } 25 | 26 | @NonNull 27 | public Data getData() { 28 | return this.data; 29 | } 30 | 31 | public void add(@NonNull Data... datas) { 32 | throw new UnsupportedOperationException(MULTI_ITEM_OPERATION_ERROR); 33 | } 34 | 35 | @Override 36 | public void add(int position, @NonNull Data data) { 37 | throw new UnsupportedOperationException(MULTI_ITEM_OPERATION_ERROR); 38 | } 39 | 40 | @Override 41 | public void remove(@NonNull Data data) { 42 | throw new UnsupportedOperationException(MULTI_ITEM_OPERATION_ERROR); 43 | } 44 | 45 | @Override 46 | public void remove(int position) { 47 | throw new UnsupportedOperationException(MULTI_ITEM_OPERATION_ERROR); 48 | } 49 | 50 | @Override 51 | public void replace(@NonNull Data data, boolean notifyAdapter) { 52 | synchronized (writeLock) { 53 | this.data = data; 54 | } 55 | if (notifyAdapter) { 56 | _notifyItemReplaced(0); 57 | } 58 | } 59 | 60 | @Override 61 | public void replace(int position, @NonNull Data data, boolean notifyAdapter) { 62 | if (position == 0) { 63 | replace(data, notifyAdapter); 64 | } else { 65 | throw new UnsupportedOperationException( 66 | "Cannot replace item from position " + position + 67 | " in SingleItemSection, since it has only one position - 0"); 68 | } 69 | } 70 | 71 | @Override 72 | public void clearAndAdd(Data... datas) { 73 | if (datas.length == 1) { 74 | this.data = datas[0]; 75 | } else { 76 | throw new UnsupportedOperationException(MULTI_ITEM_OPERATION_ERROR); 77 | } 78 | } 79 | 80 | @Override 81 | public boolean contains(Data data) { 82 | return data != null && this.data.equals(data); 83 | } 84 | 85 | @Override 86 | public void clear() { 87 | throw new UnsupportedOperationException("Clearing a SingleItemSection is prohibited. You may change the value held by calling replace()"); 88 | } 89 | 90 | @Override 91 | public int firstIndexOf(@NonNull Data data) { 92 | if (this.data.equals(data)) { 93 | return 0; 94 | } else { 95 | return -1; 96 | } 97 | } 98 | 99 | @Override 100 | public int lastIndexOf(@NonNull Data data) { 101 | return firstIndexOf(data); 102 | } 103 | 104 | @Override 105 | public final int size() { 106 | return 1; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'android-apt' 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.0" 7 | 8 | defaultConfig { 9 | applicationId "com.aashreys.sectioner.sample" 10 | minSdkVersion 9 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 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(include: ['*.jar'], dir: 'libs') 25 | testCompile 'junit:junit:4.12' 26 | compile 'com.android.support:appcompat-v7:25.0.1' 27 | compile project(':library') 28 | compile 'com.jakewharton:butterknife:8.2.1' 29 | apt 'com.jakewharton:butterknife-compiler:8.2.1' 30 | } 31 | -------------------------------------------------------------------------------- /sample/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 /Users/aashreys/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | 8 | import com.aashreys.sectioner.SectionManager; 9 | import com.aashreys.sectioner.SectionedRecyclerViewAdapter; 10 | import com.aashreys.sectioner.sample.sections.AlbumsSection; 11 | import com.aashreys.sectioner.sample.sections.FooterSection; 12 | import com.aashreys.sectioner.sample.sections.HeaderSection; 13 | import com.aashreys.sectioner.sample.sections.SeparatorSection; 14 | import com.aashreys.sectioner.sample.sections.SongsSection; 15 | 16 | import butterknife.BindView; 17 | import butterknife.ButterKnife; 18 | 19 | public class MainActivity extends AppCompatActivity { 20 | 21 | @BindView(R.id.recyclerview) RecyclerView recyclerView; 22 | private SectionedRecyclerViewAdapter adapter; 23 | private SectionManager sectionManager; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | ButterKnife.bind(this); 30 | setupRecyclerView(); 31 | createAndAddSections(); 32 | } 33 | 34 | private void setupRecyclerView() { 35 | adapter = new SectionedRecyclerViewAdapter(); 36 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 37 | recyclerView.setAdapter(adapter); 38 | } 39 | 40 | private void createAndAddSections() { 41 | // Retrieve section manager from adapter 42 | sectionManager = adapter.getSectionManager(); 43 | 44 | // Create your sections and add data 45 | HeaderSection headerSection = new HeaderSection(); 46 | SeparatorSection songsSeparatorSection = new SeparatorSection("Top Audiotracks"); 47 | 48 | SongsSection songsSection = new SongsSection(); 49 | songsSection.add(getResources().getStringArray(R.array.array_songs)); // Adding data 50 | 51 | SeparatorSection albumsSeparatorSection = new SeparatorSection("Top Records"); 52 | 53 | AlbumsSection albumsSection = new AlbumsSection(); 54 | albumsSection.add(getResources().getStringArray(R.array.array_albums)); // Adding data 55 | 56 | FooterSection footerSection = new FooterSection(); 57 | 58 | // Add sections to SectionManager. 59 | // You can also use SectionManager#add(Section) 60 | sectionManager.addAll( 61 | headerSection, 62 | songsSeparatorSection, 63 | songsSection, 64 | albumsSeparatorSection, 65 | albumsSection, 66 | footerSection 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/sections/AlbumsSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.sections; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.ViewGroup; 5 | 6 | import com.aashreys.sectioner.MultiItemSection; 7 | import com.aashreys.sectioner.sample.R; 8 | import com.aashreys.sectioner.sample.viewholders.AlbumViewHolder; 9 | 10 | /** 11 | * Created by aashreys on 09/08/16. 12 | */ 13 | public class AlbumsSection extends MultiItemSection { 14 | 15 | @Override 16 | protected AlbumViewHolder createViewHolder(ViewGroup parent) { 17 | return new AlbumViewHolder(LayoutInflater.from(parent.getContext()) 18 | .inflate(R.layout.list_album, parent, false)); 19 | } 20 | 21 | @Override 22 | protected void bindViewHolder( 23 | AlbumViewHolder holder, 24 | int sectionPosition, 25 | int adapterPosition 26 | ) { 27 | holder.onBind( 28 | dataList.get(sectionPosition), 29 | "Pink Floyd", 30 | sectionPosition, 31 | adapterPosition 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/sections/FooterSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.sections; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.ViewGroup; 5 | 6 | import com.aashreys.sectioner.SingleItemSection; 7 | import com.aashreys.sectioner.sample.R; 8 | import com.aashreys.sectioner.sample.viewholders.FooterViewHolder; 9 | 10 | /** 11 | * Created by aashreys on 09/08/16. 12 | */ 13 | public class FooterSection extends SingleItemSection { 14 | 15 | public FooterSection() { 16 | super(""); 17 | } 18 | 19 | @Override 20 | protected FooterViewHolder createViewHolder(ViewGroup parent) { 21 | return new FooterViewHolder(LayoutInflater.from(parent.getContext()) 22 | .inflate(R.layout.list_footer, parent, false)); 23 | } 24 | 25 | @Override 26 | protected void bindViewHolder( 27 | FooterViewHolder holder, 28 | int sectionPosition, 29 | int adapterPosition 30 | ) { 31 | // Do nothing 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/sections/HeaderSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.sections; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.ViewGroup; 5 | 6 | import com.aashreys.sectioner.SingleItemSection; 7 | import com.aashreys.sectioner.sample.R; 8 | import com.aashreys.sectioner.sample.viewholders.HeaderViewHolder; 9 | 10 | /** 11 | * Created by aashreys on 09/08/16. 12 | */ 13 | public class HeaderSection extends SingleItemSection { 14 | 15 | public HeaderSection() { 16 | super(""); 17 | } 18 | 19 | @Override 20 | protected HeaderViewHolder createViewHolder(ViewGroup parent) { 21 | return new HeaderViewHolder( 22 | LayoutInflater.from(parent.getContext()) 23 | .inflate(R.layout.list_header, parent, false) 24 | ); 25 | } 26 | 27 | @Override 28 | protected void bindViewHolder( 29 | HeaderViewHolder holder, 30 | int sectionPosition, 31 | int adapterPosition 32 | ) { 33 | // Do nothing 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/sections/SeparatorSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.sections; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.ViewGroup; 5 | 6 | import com.aashreys.sectioner.SingleItemSection; 7 | import com.aashreys.sectioner.sample.R; 8 | import com.aashreys.sectioner.sample.viewholders.SeparatorViewHolder; 9 | 10 | /** 11 | * Created by aashreys on 09/08/16. 12 | */ 13 | public class SeparatorSection extends SingleItemSection { 14 | 15 | public SeparatorSection(String s) { 16 | super(s); 17 | } 18 | 19 | @Override 20 | protected SeparatorViewHolder createViewHolder(ViewGroup parent) { 21 | return new SeparatorViewHolder(LayoutInflater.from(parent.getContext()) 22 | .inflate(R.layout.list_separator, parent, false)); 23 | } 24 | 25 | @Override 26 | protected void bindViewHolder( 27 | SeparatorViewHolder holder, 28 | int sectionPosition, 29 | int adapterPosition 30 | ) { 31 | holder.onBind(getData()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/sections/SongsSection.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.sections; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.ViewGroup; 5 | 6 | import com.aashreys.sectioner.MultiItemSection; 7 | import com.aashreys.sectioner.sample.R; 8 | import com.aashreys.sectioner.sample.viewholders.SongViewHolder; 9 | 10 | /** 11 | * Created by aashreys on 09/08/16. 12 | */ 13 | public class SongsSection extends MultiItemSection { 14 | 15 | @Override 16 | protected SongViewHolder createViewHolder(ViewGroup parent) { 17 | return new SongViewHolder(LayoutInflater.from(parent.getContext()) 18 | .inflate(R.layout.list_song, parent, false)); 19 | } 20 | 21 | @Override 22 | protected void bindViewHolder(SongViewHolder holder, int sectionPosition, int adapterPosition) { 23 | holder.onBind(dataList.get(sectionPosition), sectionPosition, adapterPosition); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/viewholders/AlbumViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.viewholders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | import android.widget.Toast; 8 | 9 | import com.aashreys.sectioner.sample.R; 10 | 11 | /** 12 | * Created by aashreys on 09/08/16. 13 | */ 14 | public class AlbumViewHolder extends RecyclerView.ViewHolder { 15 | 16 | private View albumView; 17 | private TextView albumNameText; 18 | private TextView artistNameText; 19 | private ImageView albumImage; 20 | 21 | public AlbumViewHolder(View itemView) { 22 | super(itemView); 23 | this.albumView = itemView; 24 | this.albumNameText = (TextView) itemView.findViewById(R.id.text_album); 25 | this.artistNameText = (TextView) itemView.findViewById(R.id.text_artist); 26 | this.albumImage = (ImageView) itemView.findViewById(R.id.image_album); 27 | } 28 | 29 | public void onBind( 30 | String albumName, 31 | String artistName, 32 | final int sectionPosition, 33 | final int adapterPosition 34 | ) { 35 | albumNameText.setText(albumName); 36 | artistNameText.setText(artistName); 37 | albumImage.setImageDrawable(albumView.getContext() 38 | .getResources() 39 | .getDrawable(R.drawable.ic_album)); 40 | albumView.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View view) { 43 | Toast.makeText( 44 | view.getContext(), 45 | String.format( 46 | "Item Section position: %s, Item Adapter Position: %s", 47 | sectionPosition, 48 | adapterPosition 49 | ), 50 | Toast.LENGTH_SHORT 51 | ).show(); 52 | } 53 | }); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/viewholders/FooterViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.viewholders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by aashreys on 09/08/16. 8 | */ 9 | public class FooterViewHolder extends RecyclerView.ViewHolder { 10 | 11 | public FooterViewHolder(View itemView) { 12 | super(itemView); 13 | } 14 | 15 | public void onBind() { 16 | // Do nothing 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/viewholders/HeaderViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.viewholders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by aashreys on 09/08/16. 8 | */ 9 | public class HeaderViewHolder extends RecyclerView.ViewHolder { 10 | 11 | public HeaderViewHolder(View itemView) { 12 | super(itemView); 13 | } 14 | 15 | public void onBind() { 16 | // Do nothing 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/viewholders/SeparatorViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.viewholders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | import com.aashreys.sectioner.sample.R; 8 | 9 | /** 10 | * Created by aashreys on 09/08/16. 11 | */ 12 | public class SeparatorViewHolder extends RecyclerView.ViewHolder { 13 | 14 | private TextView title; 15 | 16 | public SeparatorViewHolder(View itemView) { 17 | super(itemView); 18 | title = (TextView) itemView.findViewById(R.id.text_title); 19 | } 20 | 21 | public void onBind(String name) { 22 | title.setText(name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/viewholders/SongViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.viewholders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageButton; 6 | import android.widget.TextView; 7 | import android.widget.Toast; 8 | 9 | import com.aashreys.sectioner.sample.R; 10 | 11 | /** 12 | * Created by aashreys on 09/08/16. 13 | */ 14 | public class SongViewHolder extends RecyclerView.ViewHolder { 15 | 16 | private View songView; 17 | private TextView songNameText; 18 | private ImageButton playButton; 19 | 20 | public SongViewHolder(View itemView) { 21 | super(itemView); 22 | this.songView = itemView; 23 | this.songNameText = (TextView) itemView.findViewById(R.id.text_song_name); 24 | this.playButton = (ImageButton) itemView.findViewById(R.id.button_play); 25 | } 26 | 27 | public void onBind( 28 | final String songName, 29 | final int sectionPosition, 30 | final int adapterPosition 31 | ) { 32 | songNameText.setText(songName); 33 | playButton.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View view) { 36 | Toast.makeText(view.getContext(), "Playing song: " + songName, Toast.LENGTH_SHORT) 37 | .show(); 38 | } 39 | }); 40 | songView.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View view) { 43 | Toast.makeText( 44 | view.getContext(), 45 | String.format( 46 | "Item Section position: %s, Item Adapter Position: %s", 47 | sectionPosition, 48 | adapterPosition 49 | ), 50 | Toast.LENGTH_SHORT 51 | ).show(); 52 | } 53 | }); 54 | } 55 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/aashreys/sectioner/sample/views/ItemDescriptionView.java: -------------------------------------------------------------------------------- 1 | package com.aashreys.sectioner.sample.views; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.LayoutInflater; 8 | import android.widget.LinearLayout; 9 | import android.widget.TextView; 10 | 11 | import com.aashreys.sectioner.sample.R; 12 | 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | 16 | /** 17 | * Created by aashreys on 09/08/16. 18 | */ 19 | public class ItemDescriptionView extends LinearLayout { 20 | 21 | private static final String 22 | TEMPLATE_SECTION_NUMBER = "Section Number: %s", 23 | TEMPLATE_SECTION_POSITION = "Section Position: %s", 24 | TEMPLATE_ADAPTER_POSITION = "Adapter Position: %s"; 25 | 26 | @BindView(R.id.text_section_number) TextView sectionNumber; 27 | @BindView(R.id.text_section_position) TextView sectionPosition; 28 | @BindView(R.id.text_adapter_position) TextView adapterPosition; 29 | 30 | 31 | public ItemDescriptionView(Context context) { 32 | super(context); 33 | } 34 | 35 | public ItemDescriptionView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | } 38 | 39 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 40 | public ItemDescriptionView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | } 43 | 44 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 45 | public ItemDescriptionView( 46 | Context context, 47 | AttributeSet attrs, 48 | int defStyleAttr, 49 | int defStyleRes 50 | ) { 51 | super(context, attrs, defStyleAttr, defStyleRes); 52 | _init(context, attrs, defStyleAttr, defStyleRes); 53 | } 54 | 55 | private void _init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 56 | LayoutInflater.from(getContext()).inflate(R.layout.custom_item_description, this, true); 57 | ButterKnife.bind(this); 58 | } 59 | 60 | 61 | public void setSectionNumber(int num) { 62 | sectionNumber.setText(String.format(TEMPLATE_SECTION_NUMBER, num)); 63 | } 64 | 65 | public void setSectionPosition(int pos) { 66 | sectionPosition.setText(String.format(TEMPLATE_SECTION_POSITION, pos)); 67 | } 68 | 69 | public void setAdapterPosition(int pos) { 70 | adapterPosition.setText(String.format(TEMPLATE_ADAPTER_POSITION, pos)); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-hdpi/ic_album.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_audiotrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-hdpi/ic_audiotrack.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-hdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-mdpi/ic_album.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_audiotrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-mdpi/ic_audiotrack.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-mdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xhdpi/ic_album.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_audiotrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xhdpi/ic_audiotrack.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxhdpi/ic_album.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_audiotrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxhdpi/ic_audiotrack.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxxhdpi/ic_album.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_audiotrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxxhdpi/ic_audiotrack.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/drawable-xxxhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/custom_item_description.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 21 | 22 | 29 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | 32 | 33 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 23 | 24 | 34 | 35 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_separator.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_song.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 25 | 26 | 32 | 33 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashreys/Sectioner/fd70e7f630f2e348bb08a2ca65e846dc51f40390/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Lost For Words 6 | Run Like Hell 7 | Comfortably Numb 8 | Brain Damage 9 | Marooned 10 | 11 | 12 | 13 | Dark Side Of The Moon 14 | Atom Heart Mother 15 | Wish You Were Here 16 | The Division Bell 17 | The Wall 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #607D8B 4 | #455A64 5 | #03A9F4 6 | #FAFAFA 7 | #1E000000 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Sectioner 3 | 4 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library', ':sample' 2 | --------------------------------------------------------------------------------