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 | [](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