├── .gitignore ├── LICENSE ├── README.md ├── asyncexpandablelist ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ericliu │ │ └── asyncexpandablelist │ │ ├── CollectionView.java │ │ ├── CollectionViewCallbacks.java │ │ └── async │ │ ├── AsyncExpandableListView.java │ │ ├── AsyncExpandableListViewCallbacks.java │ │ └── AsyncHeaderViewHolder.java │ └── res │ └── values │ └── strings.xml ├── build.gradle ├── sampleapp ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ericliu │ │ └── asyncexpandablelistsample │ │ ├── AsyncActivity.java │ │ ├── MainActivity.java │ │ └── News.java │ └── res │ ├── drawable-hdpi │ ├── ic_arrow_down.png │ ├── ic_arrow_up.png │ ├── ic_launcher.png │ └── tile.9.png │ ├── drawable-mdpi │ ├── ic_arrow_down.png │ ├── ic_arrow_up.png │ └── ic_launcher.png │ ├── drawable-xhdpi │ ├── ic_arrow_down.png │ ├── ic_arrow_up.png │ └── ic_launcher.png │ ├── drawable-xxhdpi │ ├── ic_arrow_down.png │ ├── ic_arrow_up.png │ └── ic_launcher.png │ ├── layout │ ├── activity_async.xml │ ├── activity_main.xml │ ├── header_row_item.xml │ ├── header_row_item_async.xml │ ├── recycler_view_frag.xml │ ├── text_row_item.xml │ └── text_row_item_async.xml │ ├── menu │ └── main.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | */.idea* 3 | /.gradle* 4 | /.idea* 5 | /build/* 6 | /gradle/* 7 | /gradlew* 8 | /out* 9 | *.iml 10 | *.swp 11 | local.properties 12 | *~ 13 | build-local.gradle 14 | gradle.properties 15 | settings-local.gradle 16 | screenshots/ 17 | # Built application files 18 | *.apk 19 | *.ap_ 20 | 21 | # Files for the Dalvik VM 22 | *.dex 23 | 24 | # Java class files 25 | *.class 26 | 27 | # Generated files 28 | bin/ 29 | gen/ 30 | 31 | # Gradle files 32 | .gradle/ 33 | build/ 34 | 35 | # Local configuration file (sdk path, etc) 36 | local.properties 37 | 38 | # Proguard folder generated by Eclipse 39 | proguard/ 40 | 41 | # Log Files 42 | *.log 43 | 44 | # Android Studio Navigation editor temp files 45 | .navigation/ 46 | 47 | # Android Studio captures folder 48 | captures/ 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Eric Liu 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 | # async-expandable-list 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Async%20Expandable%20List-green.svg?style=flat)](https://android-arsenal.com/details/1/5092) 3 | 4 | ============ 5 | async-expandable-list contains 2 View classes: CollectionView and AsyncExpandableListView. 6 | 7 | 8 | ![Demo](https://cloud.githubusercontent.com/assets/3691022/19348717/0d6c98ec-919b-11e6-97c3-a8ff782a059b.gif) ![Demo](https://cloud.githubusercontent.com/assets/3691022/19406879/cb982648-92da-11e6-86bf-7c82e8505e6c.gif) 9 | 10 | Add async-expandable-list to your project 11 | ---------------------------- 12 | Gradle: 13 | ```gradle 14 | compile 'com.ericliu.asyncexpandablelist:asyncexpandablelist:1.1.0' 15 | ``` 16 | 17 | 18 | Please make sure you have jcenter() in your project's repository. Check build.gradle file under the project's root directory. Add the following lines if they are missing. 19 | ```gradle 20 | allprojects { 21 | repositories { 22 | jcenter() 23 | } 24 | } 25 | ``` 26 | 27 | Introduction 28 | ------------------- 29 | CollectionView displays a list of headers and sub-items: 30 | * Header A 31 | * item a1 32 | * item a2 33 | * Header B 34 | * item b1 35 | * item b2 36 | * item b3 37 | * Header C 38 | * item c1 39 | * item c2 40 | * item c3 41 | 42 | 43 | AsyncExpandableListView displays a list of headers and loads a sub-list under a header when a header item is clicked. The loading of sub-items can be done asynchronously and there are callbacks to populate the data into the list when it's done loading. 44 | 45 | 46 | 1. CollectionView in 3 steps 47 | ------------------- 48 | 1. Add the CollectionView to the layout.xml file where you want to display the list (Optional, population the CollectionView class in java has the same result) 49 | ```xml 50 | 54 | 55 | ``` 56 | 57 | 2. Pouplating data 58 | * find the CollectionView instance and call setCollectionCallbacks() to setup callbacks for the CollectionView, which will be responsible for creating ViewHolders and binding data into the ViewHoders - works the same as the RecyclerView.Adapter except that you don't have to worry about view types. 59 | * Create a CollectionView.Inventory instance, the Inventory instance represents the whole data structure that's gonna be populated into the list. 60 | 61 | 62 | ```java 63 | public class MainActivity extends Activity implements CollectionViewCallbacks { 64 | private CollectionView mCollectionView; 65 | private CollectionView.Inventory inventory; 66 | 67 | @Override 68 | protected void onCreate(Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | setContentView(R.layout.activity_main); 71 | 72 | mCollectionView = (CollectionView) findViewById(R.id.collectionView); 73 | mCollectionView.setCollectionCallbacks(this); 74 | 75 | // the inventory represent all the whole data structure that's gonna be populated into the list. 76 | inventory = new CollectionView.Inventory<>(); 77 | 78 | ``` 79 | * Create InventoryGroup intances and add header item and sub-items into the InventoryGroup instance. 80 | Note the the newGroup(int groupOrdinal) method provided in the Inventory class requires an integer parameter: groupOrdinal. 81 | All the groups will be displayed in the list in an ascending order on the groupOrdinal. 82 | An InventoryGroup represents a header item and all sub-items under that header in the list. 83 | 84 | 85 | ```java 86 | int groupOrdinal = 0; // groupOrdinal dictates the sequence of groups to be displayed in the list 87 | CollectionView.InventoryGroup group1 = inventory.newGroup(groupOrdinal); 88 | 89 | // creating objects to be populated into the list. 90 | News news1 = new News(); 91 | ... 92 | News news2 = new News(); ...... 93 | ....... 94 | 95 | // set the header item, in this case, it is simply a String. 96 | group1.setHeaderItem("Top Stories"); 97 | // add items under this header. 98 | group1.addItem(news1); 99 | group1.addItem(news2); 100 | group1.addItem(news3); 101 | .... 102 | 103 | ``` 104 | 105 | * Call updateInventory() to display the data structure we just created 106 | ```java 107 | mCollectionView.updateInventory(inventory); 108 | ``` 109 | All done, the list will display the exact header-items structure. 110 | 111 | 2. AsyncExpandableListView in 3 steps 112 | ------------------- 113 | 114 | 1. add AsyncExpandableListView to layout.xml file where you want to display the expandable list. (Optional, population the AsyncExpandableListView class in java has the same result). 115 | ```xml 116 | 120 | ``` 121 | 2. Populating data 122 | * find the AsyncExpandableListView and call setCallbacks() and supply an AsyncExpandableListViewCallbacks instance to the view. The callbacks will handle the creation of ViewHolders and binding data to the ViewHolders. 123 | ```java 124 | public class AsyncActivity extends Activity implements AsyncExpandableListViewCallbacks { 125 | 126 | private AsyncExpandableListView mAsyncExpandableListView; 127 | private CollectionView.Inventory inventory; 128 | 129 | 130 | @Override 131 | protected void onCreate(Bundle savedInstanceState) { 132 | super.onCreate(savedInstanceState); 133 | setContentView(R.layout.activity_async); 134 | mAsyncExpandableListView = (AsyncExpandableListView) findViewById(R.id.asyncExpandableCollectionView); 135 | mAsyncExpandableListView.setCallbacks(this); 136 | 137 | ``` 138 | 139 | 140 | * In particular the ``` void onStartLoadingGroup(int groupOrdinal) ``` method in the AsyncExpandableListViewCallbacks will be triggered on the header item click events, which gives the client a change to trigger loading sub-item data calls. When the call comes back, the client should call the method ``` onFinishLoadingGroup(mGroupOrdinal, items)``` on the AsyncExpandableListView instance to display the data as well as updating UI. 141 | * The steps to add groups are the same as CollectionView mentioned above, but we don't need to add sub-items to groups at this point because only headers will be shown in the beginning in an expandable list, as the code snippet showed below: 142 | ```java 143 | inventory = new CollectionView.Inventory<>(); 144 | 145 | CollectionView.InventoryGroup group1 = inventory.newGroup(0); // groupOrdinal is the smallest, displayed first 146 | group1.setHeaderItem("Top Stories"); 147 | 148 | 149 | CollectionView.InventoryGroup group2 = inventory.newGroup(2); // 2 is the second smallest ordinal, displayed second 150 | group2.setHeaderItem("World"); 151 | 152 | 153 | CollectionView.InventoryGroup group3 = inventory.newGroup(3); 154 | group3.setHeaderItem("Australia"); 155 | 156 | CollectionView.InventoryGroup group4 = inventory.newGroup(4); 157 | group4.setHeaderItem("International"); 158 | 159 | CollectionView.InventoryGroup group5 = inventory.newGroup(5); 160 | group5.setHeaderItem("Businesses"); 161 | 162 | CollectionView.InventoryGroup group6 = inventory.newGroup(6); 163 | group6.setHeaderItem("Technology"); 164 | 165 | mAsyncExpandableListView.updateInventory(inventory); 166 | 167 | ``` 168 | 169 | 3. Handle the async calls 170 | * Making the call to load all sub-items under a header in the method onStartLoadingGroup() in the AsyncExpandableListViewCallbacks. 171 | 172 | ```java 173 | @Override 174 | public void onStartLoadingGroup(int groupOrdinal) { 175 | new LoadDataTask(groupOrdinal, mAsyncExpandableListView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 176 | } 177 | ``` 178 | * When the data come back, call ``` onFinishLoadingGroup(mGroupOrdinal, items); ``` to display data. 179 | ```java 180 | mAsyncExpandableListView.onFinishLoadingGroup(mGroupOrdinal, items); 181 | ``` 182 | 183 | References: 184 | ------------------- 185 | Inspired by CollectionView in Google iosche 186 | 187 | https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/CollectionView.java 188 | -------------------------------------------------------------------------------- /asyncexpandablelist/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /asyncexpandablelist/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'com.jfrog.bintray' 4 | version = "1.1.0" 5 | 6 | android { 7 | compileSdkVersion rootProject.compileSdk 8 | buildToolsVersion rootProject.buildToolsVersion 9 | 10 | defaultConfig { 11 | minSdkVersion rootProject.minSdk 12 | targetSdkVersion rootProject.targetSdk 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | buildToolsVersion '25.0.0' 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | compile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion" 30 | 31 | testCompile 'junit:junit:4.12' 32 | 33 | } 34 | 35 | 36 | def siteUrl = 'https://github.com/Ericliu001/async-expandable-list' //Homepage URL of the library 37 | def gitUrl = 'https://github.com/Ericliu001/async-expandable-list.git' //Git repository url 38 | def issueUrl = 'https://github.com/Ericliu001/async-expandable-list/issues' //issue url of the library 39 | group = 'com.ericliu.asyncexpandablelist' // 40 | 41 | 42 | install { 43 | repositories.mavenInstaller { 44 | // This generates POM.xml with proper parameters 45 | pom { 46 | project { 47 | packaging 'aar' 48 | // Add your description here 49 | name 'A custom recyclerview that handles expandable list and async loading' 50 | url siteUrl 51 | // Set your license 52 | licenses { 53 | license { 54 | name 'The MIT License (MIT)' 55 | url 'https://opensource.org/licenses/MIT' 56 | } 57 | } 58 | developers { 59 | developer { 60 | id 'ericliu001' //your user ID 61 | name 'Eric Liu' //your name 62 | email 'eric.liu.developer@gmail.com' //your email 63 | } 64 | } 65 | scm { 66 | connection gitUrl 67 | developerConnection gitUrl 68 | url siteUrl 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | 76 | task sourcesJar(type: Jar) { 77 | from android.sourceSets.main.java.srcDirs 78 | classifier = 'sources' 79 | } 80 | task javadoc(type: Javadoc) { 81 | source = android.sourceSets.main.java.srcDirs 82 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 83 | } 84 | task javadocJar(type: Jar, dependsOn: javadoc) { 85 | classifier = 'javadoc' 86 | from javadoc.destinationDir 87 | } 88 | artifacts { 89 | archives javadocJar 90 | archives sourcesJar 91 | } 92 | 93 | Properties properties = new Properties() 94 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 95 | 96 | bintray { 97 | user = properties.getProperty("bintray.user") 98 | key = properties.getProperty("bintray.apikey") 99 | configurations = ['archives'] 100 | pkg { 101 | 102 | repo = 'maven' //Bintray repository 103 | name = 'async-expandable-list' 104 | desc = 'A custom recyclerview that handles expandable list and async loading' //Project description 105 | websiteUrl = siteUrl 106 | vcsUrl = gitUrl 107 | issueTrackerUrl = issueUrl 108 | licenses = ["MIT"] 109 | labels = ['android'] //标签 110 | publish = true 111 | publicDownloadNumbers = true 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /asyncexpandablelist/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/asyncexpandablelist/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /asyncexpandablelist/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 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 | -------------------------------------------------------------------------------- /asyncexpandablelist/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 | -------------------------------------------------------------------------------- /asyncexpandablelist/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 | -------------------------------------------------------------------------------- /asyncexpandablelist/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/ericliu/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 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/java/com/ericliu/asyncexpandablelist/CollectionView.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelist; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.util.SparseArray; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by Eric Liu on 18/01/2016. 17 | */ 18 | public class CollectionView extends RecyclerView { 19 | private static final String TAG = CollectionView.class.getSimpleName(); 20 | private static final int VIEWTYPE_HEADER = 0; 21 | private static final int VIEW_TYPE_NON_HEADER = 10; 22 | protected final LinearLayoutManager mLinearLayoutManager; 23 | 24 | protected Inventory mInventory = new Inventory<>(); 25 | private CollectionViewCallbacks mCallbacks = null; 26 | private MyListAdapter mAdapter = null; 27 | 28 | 29 | public CollectionView(Context context) { 30 | this(context, null); 31 | } 32 | 33 | public CollectionView(Context context, AttributeSet attrs) { 34 | this(context, attrs, 0); 35 | } 36 | 37 | 38 | public CollectionView(Context context, AttributeSet attrs, int defStyle) { 39 | super(context, attrs, defStyle); 40 | mLinearLayoutManager = new LinearLayoutManager(getContext()); 41 | mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 42 | this.setLayoutManager(mLinearLayoutManager); 43 | mAdapter = new MyListAdapter(); 44 | setAdapter(mAdapter); 45 | } 46 | 47 | 48 | public void setCollectionCallbacks(CollectionViewCallbacks adapter) { 49 | mCallbacks = adapter; 50 | } 51 | 52 | 53 | 54 | 55 | public void updateInventory(final Inventory inventory) { 56 | mInventory = new Inventory<>(inventory); 57 | mAdapter.notifyDataSetChanged(); 58 | } 59 | 60 | 61 | 62 | public T1 getHeader(int groupOrdinal) { 63 | InventoryGroup group = mInventory.mGroups.get(groupOrdinal); 64 | if (group != null) { 65 | return group.getHeaderItem(); 66 | } else { 67 | return null; 68 | } 69 | } 70 | 71 | public void addGroup(InventoryGroup group) { 72 | mInventory.addGroup(group); 73 | int itemCountBeforeGroup = mInventory.getRowCountBeforeGroup(group); 74 | 75 | mAdapter.notifyItemRangeInserted(itemCountBeforeGroup, group.getRowCount()); 76 | } 77 | 78 | public void addItemsInGroup(int groupOrdinal, List items) { 79 | InventoryGroup group = mInventory.findGroup(groupOrdinal); 80 | int rowCountBeforeAddingItems = group.getRowCount(); 81 | int itemCountBeforeGroup = mInventory.getRowCountBeforeGroup(group); 82 | 83 | group.addItems(items); 84 | 85 | mAdapter.notifyItemRangeInserted(itemCountBeforeGroup + rowCountBeforeAddingItems, items.size()); 86 | } 87 | 88 | 89 | public void removeAllItemsInGroup(int groupOrdinal) { 90 | InventoryGroup group = mInventory.findGroup(groupOrdinal); 91 | int itemCount = group.mItems.size(); 92 | int itemCountBeforeGroup = mInventory.getRowCountBeforeGroup(group); 93 | group.mItems.clear(); 94 | 95 | mAdapter.notifyItemRangeRemoved(itemCountBeforeGroup + 1, itemCount); 96 | 97 | } 98 | 99 | 100 | private final class MyListAdapter extends Adapter { 101 | private MyListAdapter() { 102 | } 103 | 104 | @Override 105 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 106 | return getRowViewHolder(parent, viewType); 107 | } 108 | 109 | @Override 110 | public void onBindViewHolder(ViewHolder holder, int position) { 111 | populatRoweData(holder, position); 112 | } 113 | 114 | @Override 115 | public int getItemViewType(int position) { 116 | RowInformation rowInfo = computeRowContent(position); 117 | if (rowInfo.isComputedSuccessful) { 118 | if (rowInfo.isHeader) { 119 | return VIEWTYPE_HEADER - mInventory.mGroups.indexOfKey(rowInfo.groupOrdinal); 120 | } else { 121 | return VIEW_TYPE_NON_HEADER + mInventory.mGroups.indexOfKey(rowInfo.groupOrdinal); 122 | } 123 | 124 | } else { 125 | Log.e(TAG, "Invalid row passed to getItemViewType: " + position); 126 | return 0; 127 | } 128 | } 129 | 130 | 131 | @Override 132 | public int getItemCount() { 133 | int rowCount = 0; 134 | 135 | for (int i = 0; i < mInventory.mGroups.size(); i++) { 136 | int key = mInventory.mGroups.keyAt(i); 137 | InventoryGroup group = mInventory.mGroups.get(key); 138 | int thisGroupRowCount = group.getRowCount(); 139 | rowCount += thisGroupRowCount; 140 | } 141 | 142 | return rowCount; 143 | } 144 | } 145 | 146 | protected RowInformation populatRoweData(ViewHolder holder, int position) { 147 | if (mCallbacks == null) { 148 | return null; 149 | } 150 | 151 | RowInformation rowInfo = computeRowContent(position); 152 | if (!rowInfo.isComputedSuccessful) { 153 | return null; 154 | } 155 | 156 | if (rowInfo.isHeader) { 157 | mCallbacks.bindCollectionHeaderView(getContext(), holder, rowInfo.groupOrdinal, rowInfo.group.getHeaderItem()); 158 | } else { 159 | T2 item = rowInfo.group.getItem(rowInfo.positionInGroup); 160 | mCallbacks.bindCollectionItemView(getContext(), holder, rowInfo.groupOrdinal, item); 161 | } 162 | 163 | return rowInfo; 164 | } 165 | 166 | private ViewHolder getRowViewHolder(ViewGroup parent, final int viewType) { 167 | ViewHolder placeHolder = new ViewHolder(new View(getContext())) { 168 | @Override 169 | public String toString() { 170 | return "Invalid Item, view type: " + viewType; 171 | } 172 | }; 173 | if (mCallbacks == null) { 174 | Log.e(TAG, "Call to makeRow without an adapter installed"); 175 | return placeHolder; 176 | } 177 | 178 | 179 | ViewHolder holder; 180 | if (viewType <= VIEWTYPE_HEADER) { 181 | int groupIndex = VIEWTYPE_HEADER - viewType; 182 | int key = mInventory.mGroups.keyAt(groupIndex); 183 | int groupOrdinal = mInventory.mGroups.get(key).mOrdinal; 184 | 185 | // return header ViewHolder 186 | holder = mCallbacks.newCollectionHeaderView(getContext(), groupOrdinal, parent); 187 | } else { 188 | int groupIndex = viewType - VIEW_TYPE_NON_HEADER; 189 | int key = mInventory.mGroups.keyAt(groupIndex); 190 | int groupOrdinal = mInventory.mGroups.get(key).mOrdinal; 191 | // return item ViewHolder 192 | holder = mCallbacks.newCollectionItemView(getContext(), groupOrdinal, parent); 193 | } 194 | 195 | if (holder != null) { 196 | return holder; 197 | } else { 198 | return placeHolder; 199 | } 200 | 201 | } 202 | 203 | 204 | protected static class RowInformation { 205 | boolean isComputedSuccessful = false; 206 | int row; 207 | boolean isHeader; 208 | int groupOrdinal; 209 | InventoryGroup group; 210 | int positionInGroup; 211 | 212 | public boolean isHeader() { 213 | return isHeader; 214 | } 215 | 216 | public int getGroupOrdinal() { 217 | return groupOrdinal; 218 | } 219 | 220 | public int getPositionInGroup() { 221 | return positionInGroup; 222 | } 223 | } 224 | 225 | 226 | protected RowInformation computeRowContent(int row) { 227 | RowInformation result = new RowInformation<>(); 228 | int rowCounter = 0; 229 | int positionInGroup; 230 | 231 | 232 | for (int i = 0; i < mInventory.mGroups.size(); i++) { 233 | int key = mInventory.mGroups.keyAt(i); 234 | InventoryGroup group = mInventory.mGroups.get(key); 235 | if (rowCounter == row) { 236 | // row is a group header 237 | result.isComputedSuccessful = true; 238 | result.row = row; 239 | result.isHeader = true; 240 | result.groupOrdinal = group.mOrdinal; 241 | result.group = group; 242 | result.positionInGroup = -1; 243 | return result; 244 | } 245 | rowCounter++; // incremented by 1 because it just past the Header row 246 | 247 | positionInGroup = 0; 248 | while (positionInGroup < group.mItems.size()) { 249 | if (rowCounter == row) { 250 | // this is the row we are looking for 251 | result.isComputedSuccessful = true; 252 | result.row = row; 253 | result.isHeader = false; 254 | result.groupOrdinal = group.mOrdinal; 255 | result.group = group; 256 | result.positionInGroup = positionInGroup; 257 | return result; 258 | } 259 | 260 | // move to the next row 261 | positionInGroup++; 262 | rowCounter++; 263 | } 264 | } 265 | 266 | return result; 267 | } 268 | 269 | 270 | /** 271 | * Represents a group of items with a header to be displayed in the {@link CollectionView}. 272 | */ 273 | public final static class InventoryGroup { 274 | 275 | final private int mOrdinal; 276 | 277 | private T1 mHeaderItem; 278 | private ArrayList mItems = new ArrayList<>(); 279 | 280 | 281 | private InventoryGroup(int oridinal) { 282 | mOrdinal = oridinal; 283 | } 284 | 285 | 286 | public int getOrdinal() { 287 | return mOrdinal; 288 | } 289 | 290 | public T1 getHeaderItem() { 291 | return mHeaderItem; 292 | } 293 | 294 | public InventoryGroup setHeaderItem(T1 headerItem) { 295 | mHeaderItem = headerItem; 296 | return this; 297 | } 298 | 299 | 300 | public void addItem(T2 item) { 301 | mItems.add(item); 302 | } 303 | 304 | 305 | public int getRowCount() { 306 | return 1 + mItems.size(); 307 | } 308 | 309 | 310 | public T2 getItem(int index) { 311 | return mItems.get(index); 312 | } 313 | 314 | public void addItems(List items) { 315 | mItems.addAll(items); 316 | } 317 | 318 | 319 | } 320 | 321 | 322 | /** 323 | * Represents the data of the items to display in the {@link CollectionView}. 324 | * This is defined as a list of {@link InventoryGroup} which represents a group of items with a 325 | * header. 326 | */ 327 | public final static class Inventory { 328 | private SparseArray> mGroups = new SparseArray<>(); 329 | 330 | 331 | public Inventory() { 332 | } 333 | 334 | private Inventory(Inventory copyFrom) { 335 | mGroups = copyFrom.mGroups.clone(); 336 | } 337 | 338 | private void addGroup(InventoryGroup group) { 339 | mGroups.put(group.mOrdinal, group); 340 | } 341 | 342 | public InventoryGroup newGroup(int groupOrdinal) { 343 | InventoryGroup group = new InventoryGroup<>(groupOrdinal); 344 | addGroup(group); 345 | return group; 346 | } 347 | 348 | public InventoryGroup putGroup(int groupOrdinal) { 349 | return newGroup(groupOrdinal); 350 | } 351 | 352 | 353 | private InventoryGroup findGroup(int groupOrdinal) { 354 | return mGroups.get(groupOrdinal); 355 | } 356 | 357 | 358 | public int getTotalItemCount() { 359 | int total = 0; 360 | 361 | for (int i = 0; i < mGroups.size(); i++) { 362 | int key = mGroups.keyAt(i); 363 | total += mGroups.get(key).mItems.size(); 364 | } 365 | return total; 366 | } 367 | 368 | public int getGroupCount() { 369 | return mGroups.size(); 370 | } 371 | 372 | public int getGroupIndex(int groupOrdinal) { 373 | for (int i = 0; i < mGroups.size(); i++) { 374 | int key = mGroups.keyAt(i); 375 | if (mGroups.get(key).mOrdinal == groupOrdinal) { 376 | return i; 377 | } 378 | } 379 | return -1; 380 | } 381 | 382 | public int getRowCountBeforeGroup(InventoryGroup group) { 383 | return getRowCountBeforeGroup(group.mOrdinal); 384 | } 385 | 386 | public int getRowCountBeforeGroup(int groupOrdinal) { 387 | int count = 0; 388 | for (int i = 0; i < mGroups.size(); i++) { 389 | if (groupOrdinal == mGroups.keyAt(i)) { 390 | break; 391 | } 392 | int key = mGroups.keyAt(i); 393 | count += mGroups.get(key).getRowCount(); 394 | } 395 | return count; 396 | } 397 | 398 | 399 | public SparseArray> getGroups() { 400 | return mGroups; 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/java/com/ericliu/asyncexpandablelist/CollectionViewCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelist;/* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import android.content.Context; 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.ViewGroup; 21 | 22 | /** 23 | * Defines an interface to the callbacks that a {@link CollectionView} will be called to create each 24 | * elements of the collection. 25 | */ 26 | public interface CollectionViewCallbacks { 27 | 28 | /** 29 | * Returns a new custom View that will be used for each of the collection group headers. 30 | */ 31 | RecyclerView.ViewHolder newCollectionHeaderView(Context context, int groupOrdinal, ViewGroup parent); 32 | 33 | 34 | /** 35 | * Returns a new custom View that will be used for each of the collection item. 36 | * 37 | * @param context 38 | * @param groupOrdinal - the groupOrdinal decides the sequence of groups being displayed, the smallest int is displayed first and in an asending order 39 | * @param parent 40 | * @return 41 | */ 42 | RecyclerView.ViewHolder newCollectionItemView(Context context, int groupOrdinal, ViewGroup parent); 43 | 44 | /** 45 | * Binds the given data (like the header label) with the given collection group header View. 46 | */ 47 | void bindCollectionHeaderView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, T1 headerItem); 48 | 49 | /** 50 | * Binds the given data with the given collection item View. 51 | */ 52 | void bindCollectionItemView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, T2 item); 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/java/com/ericliu/asyncexpandablelist/async/AsyncExpandableListView.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelist.async; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.AttributeSet; 6 | import android.view.ViewGroup; 7 | 8 | import com.ericliu.asyncexpandablelist.CollectionView; 9 | import com.ericliu.asyncexpandablelist.CollectionViewCallbacks; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.WeakHashMap; 14 | 15 | 16 | /** 17 | * Created by ericliu on 6/10/2016. 18 | */ 19 | 20 | public class AsyncExpandableListView extends CollectionView { 21 | 22 | private AsyncExpandableListViewCallbacks mCallbacks; 23 | protected Map mOnGroupStateChangeListeners = new WeakHashMap<>(); 24 | protected int expandedGroupOrdinal = -1; 25 | 26 | 27 | public AsyncExpandableListView(Context context) { 28 | super(context); 29 | } 30 | 31 | public AsyncExpandableListView(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | } 34 | 35 | public AsyncExpandableListView(Context context, AttributeSet attrs, int defStyle) { 36 | super(context, attrs, defStyle); 37 | } 38 | 39 | 40 | @Override 41 | protected CollectionView.RowInformation populatRoweData(RecyclerView.ViewHolder holder, int position) { 42 | CollectionView.RowInformation rowInfo = super.populatRoweData(holder, position); 43 | if (rowInfo.isHeader()) { 44 | mOnGroupStateChangeListeners.put((AsyncHeaderViewHolder) holder, rowInfo.getGroupOrdinal()); 45 | } 46 | 47 | return rowInfo; 48 | } 49 | 50 | public interface OnGroupStateChangeListener { 51 | 52 | void onGroupStartExpending(); 53 | 54 | void onGroupExpanded(); 55 | 56 | void onGroupCollapsed(); 57 | } 58 | 59 | 60 | public void onGroupClicked(int groupOrdinal) { 61 | if (groupOrdinal != expandedGroupOrdinal) { 62 | onStartExpandingGroup(groupOrdinal); 63 | } else { 64 | collapseGroup(groupOrdinal); 65 | } 66 | 67 | } 68 | 69 | 70 | public void setCallbacks(final AsyncExpandableListViewCallbacks callbacks) { 71 | CollectionViewCallbacks collectionViewCallbacks = new CollectionViewCallbacks() { 72 | @Override 73 | public ViewHolder newCollectionHeaderView(Context context, int groupOrdinal, ViewGroup parent) { 74 | return callbacks.newCollectionHeaderView(context, groupOrdinal, parent); 75 | } 76 | 77 | @Override 78 | public ViewHolder newCollectionItemView(Context context, int groupOrdinal, ViewGroup parent) { 79 | return callbacks.newCollectionItemView(context, groupOrdinal, parent); 80 | } 81 | 82 | @Override 83 | public void bindCollectionHeaderView(Context context, ViewHolder holder, int groupOrdinal, T1 headerItem) { 84 | callbacks.bindCollectionHeaderView(context, (AsyncHeaderViewHolder) holder, groupOrdinal, headerItem); 85 | } 86 | 87 | @Override 88 | public void bindCollectionItemView(Context context, ViewHolder holder, int groupOrdinal, T2 item) { 89 | callbacks.bindCollectionItemView(context, holder, groupOrdinal, item); 90 | } 91 | }; 92 | setCollectionCallbacks(collectionViewCallbacks); 93 | mCallbacks = callbacks; 94 | } 95 | 96 | protected void collapseGroup(int groupOrdinal) { 97 | expandedGroupOrdinal = -1; 98 | removeAllItemsInGroup(groupOrdinal); 99 | for (OnGroupStateChangeListener onGroupStateChangeListener : mOnGroupStateChangeListeners.keySet()) { 100 | if (mOnGroupStateChangeListeners.get(onGroupStateChangeListener) == groupOrdinal) { 101 | onGroupStateChangeListener.onGroupCollapsed(); 102 | } 103 | } 104 | } 105 | 106 | 107 | protected void onStartExpandingGroup(int groupOrdinal) { 108 | int ordinal = 0; 109 | for (int i = 0; i < mInventory.getGroups().size(); i++) { 110 | ordinal = mInventory.getGroups().keyAt(i); 111 | if (ordinal != groupOrdinal) { 112 | collapseGroup(ordinal); 113 | } 114 | } 115 | 116 | expandedGroupOrdinal = groupOrdinal; 117 | for (OnGroupStateChangeListener onGroupStateChangeListener : mOnGroupStateChangeListeners.keySet()) { 118 | if (mOnGroupStateChangeListeners.get(onGroupStateChangeListener) == groupOrdinal) { 119 | onGroupStateChangeListener.onGroupStartExpending(); 120 | } 121 | } 122 | mCallbacks.onStartLoadingGroup(groupOrdinal); 123 | } 124 | 125 | 126 | public boolean onFinishLoadingGroup(int groupOrdinal, List items) { 127 | if (expandedGroupOrdinal < 0 || groupOrdinal != expandedGroupOrdinal) { 128 | return false; 129 | } 130 | 131 | addItemsInGroup(expandedGroupOrdinal, items); 132 | for (OnGroupStateChangeListener onGroupStateChangeListener : mOnGroupStateChangeListeners.keySet()) { 133 | if (mOnGroupStateChangeListeners.get(onGroupStateChangeListener) == expandedGroupOrdinal) { 134 | onGroupStateChangeListener.onGroupExpanded(); 135 | } 136 | } 137 | 138 | return true; 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/java/com/ericliu/asyncexpandablelist/async/AsyncExpandableListViewCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelist.async; 2 | 3 | 4 | import android.content.Context; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.ViewGroup; 7 | 8 | /** 9 | * Created by ericliu on 11/10/16. 10 | */ 11 | 12 | public interface AsyncExpandableListViewCallbacks { 13 | 14 | void onStartLoadingGroup(int groupOrdinal); 15 | 16 | /** 17 | * Returns a new custom View that will be used for each of the collection group headers. 18 | */ 19 | AsyncHeaderViewHolder newCollectionHeaderView(Context context, int groupOrdinal, ViewGroup parent); 20 | 21 | 22 | /** 23 | * Returns a new custom View that will be used for each of the collection item. 24 | * 25 | * @param context 26 | * @param groupOrdinal - the groupOrdinal decides the sequence of groups being displayed, the smallest int is displayed first and in an asending order 27 | * @param parent 28 | * @return 29 | */ 30 | RecyclerView.ViewHolder newCollectionItemView(Context context, int groupOrdinal, ViewGroup parent); 31 | 32 | /** 33 | * Binds the given data (like the header label) with the given collection group header View. 34 | */ 35 | void bindCollectionHeaderView(Context context, AsyncHeaderViewHolder holder, int groupOrdinal, T1 headerItem); 36 | 37 | /** 38 | * Binds the given data with the given collection item View. 39 | */ 40 | void bindCollectionItemView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, T2 item); 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/java/com/ericliu/asyncexpandablelist/async/AsyncHeaderViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelist.async; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by ericliu on 12/10/16. 8 | */ 9 | 10 | public abstract class AsyncHeaderViewHolder extends RecyclerView.ViewHolder implements AsyncExpandableListView.OnGroupStateChangeListener { 11 | private final int mGroupOrdinal; 12 | private final AsyncExpandableListView mAsyncExpandableListView; 13 | 14 | public AsyncHeaderViewHolder(View itemView, int groupOrdinal, AsyncExpandableListView asyncExpandableListView) { 15 | super(itemView); 16 | mGroupOrdinal = groupOrdinal; 17 | mAsyncExpandableListView = asyncExpandableListView; 18 | itemView.setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | mAsyncExpandableListView.onGroupClicked(mGroupOrdinal); 22 | onItemClick(v); 23 | } 24 | }); 25 | } 26 | 27 | /** 28 | * triggered by onClick event, to be overriden. 29 | * @param view 30 | */ 31 | public void onItemClick(View view){} 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /asyncexpandablelist/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AsyncExpandableList 3 | 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | mavenCentral() 20 | maven { 21 | url 'https://oss.sonatype.org/content/groups/public' 22 | } 23 | } 24 | } 25 | 26 | 27 | 28 | project.ext.supportLibraryVersion = '24.2.0' 29 | project.ext.buildToolsVersion = '24.0.2' 30 | 31 | project.ext.targetSdk = 21 32 | project.ext.minSdk = 15 33 | project.ext.compileSdk = 24 34 | -------------------------------------------------------------------------------- /sampleapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | android { 3 | compileSdkVersion rootProject.compileSdk 4 | buildToolsVersion rootProject.buildToolsVersion 5 | 6 | defaultConfig { 7 | applicationId "com.ericliu.asyncexpandablelistsample" 8 | minSdkVersion rootProject.minSdk 9 | targetSdkVersion rootProject.targetSdk 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 | lintOptions { 20 | abortOnError false 21 | } 22 | 23 | buildToolsVersion '25.0.0' 24 | } 25 | 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | compile project(':asyncexpandablelist') 30 | compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion" 31 | compile "com.android.support:support-v4:$rootProject.supportLibraryVersion" 32 | compile "com.android.support:cardview-v7:$rootProject.supportLibraryVersion" 33 | } 34 | -------------------------------------------------------------------------------- /sampleapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 23 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sampleapp/src/main/java/com/ericliu/asyncexpandablelistsample/AsyncActivity.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelistsample; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.ProgressBar; 13 | import android.widget.TextView; 14 | 15 | import com.ericliu.asyncexpandablelist.CollectionView; 16 | import com.ericliu.asyncexpandablelist.async.AsyncExpandableListView; 17 | import com.ericliu.asyncexpandablelist.async.AsyncExpandableListViewCallbacks; 18 | import com.ericliu.asyncexpandablelist.async.AsyncHeaderViewHolder; 19 | 20 | import java.lang.ref.WeakReference; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | 25 | public class AsyncActivity extends Activity implements AsyncExpandableListViewCallbacks { 26 | 27 | private AsyncExpandableListView mAsyncExpandableListView; 28 | private CollectionView.Inventory inventory; 29 | 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_async); 35 | mAsyncExpandableListView = (AsyncExpandableListView) findViewById(R.id.asyncExpandableCollectionView); 36 | mAsyncExpandableListView.setCallbacks(this); 37 | 38 | inventory = new CollectionView.Inventory<>(); 39 | 40 | CollectionView.InventoryGroup group1 = inventory.newGroup(0); // groupOrdinal is the smallest, displayed first 41 | group1.setHeaderItem("Top Stories"); 42 | 43 | 44 | CollectionView.InventoryGroup group2 = inventory.newGroup(2); 45 | group2.setHeaderItem("World"); 46 | 47 | 48 | CollectionView.InventoryGroup group3 = inventory.newGroup(3); 49 | group3.setHeaderItem("Australia"); 50 | 51 | CollectionView.InventoryGroup group4 = inventory.newGroup(4); 52 | group4.setHeaderItem("International"); 53 | 54 | CollectionView.InventoryGroup group5 = inventory.newGroup(5); 55 | group5.setHeaderItem("Businesses"); 56 | 57 | CollectionView.InventoryGroup group6 = inventory.newGroup(6); 58 | group6.setHeaderItem("Technology"); 59 | 60 | CollectionView.InventoryGroup group7 = inventory.newGroup(7); 61 | group7.setHeaderItem("Environment"); 62 | 63 | CollectionView.InventoryGroup group8 = inventory.newGroup(8); 64 | group8.setHeaderItem("Health"); 65 | 66 | CollectionView.InventoryGroup group9 = inventory.newGroup(9); 67 | group9.setHeaderItem("Science"); 68 | 69 | CollectionView.InventoryGroup group10 = inventory.newGroup(10); 70 | group10.setHeaderItem("Sports"); 71 | 72 | CollectionView.InventoryGroup group11 = inventory.newGroup(11); 73 | group11.setHeaderItem("Entertainment"); 74 | 75 | CollectionView.InventoryGroup group12 = inventory.newGroup(12); 76 | group12.setHeaderItem("Politics"); 77 | 78 | mAsyncExpandableListView.updateInventory(inventory); 79 | } 80 | 81 | @Override 82 | public void onStartLoadingGroup(int groupOrdinal) { 83 | new LoadDataTask(groupOrdinal, mAsyncExpandableListView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 84 | } 85 | 86 | 87 | 88 | 89 | private static class LoadDataTask extends AsyncTask { 90 | 91 | private final int mGroupOrdinal; 92 | private WeakReference> listviewRef = null; 93 | 94 | public LoadDataTask(int groupOrdinal, AsyncExpandableListView listview) { 95 | mGroupOrdinal = groupOrdinal; 96 | listviewRef = new WeakReference<>(listview); 97 | } 98 | 99 | @Override 100 | protected Void doInBackground(Void... params) { 101 | try { 102 | Thread.sleep(1500); 103 | } catch (InterruptedException e) { 104 | e.printStackTrace(); 105 | } 106 | return null; 107 | } 108 | 109 | 110 | @Override 111 | protected void onPostExecute(Void aVoid) { 112 | List items = new ArrayList<>(); 113 | News news = new News(); 114 | news.setNewsTitle("Lawyers meet voluntary pro bono target for first time since 2013"); 115 | news.setNewsBody("A voluntary target for the amount of pro bono work done by Australian lawyers has been met for the first time since 2013. Key points: The Australian Pro Bono Centre's asks lawyers to do 35 hours of free community work a year; Pro bono services can help ...\n"); 116 | items.add(news); 117 | 118 | news = new News(); 119 | news.setNewsTitle("HSC 2016: 77000 students to sit first exams across NSW"); 120 | news.setNewsBody("More than 77,000 NSW high school students will sit their first HSC exams this week as one of the final cohorts to sit the test before the NSW government enacts sweeping reforms across the state."); 121 | items.add(news); 122 | 123 | if (listviewRef.get() != null) { 124 | listviewRef.get().onFinishLoadingGroup(mGroupOrdinal, items); 125 | } 126 | } 127 | 128 | } 129 | 130 | @Override 131 | public AsyncHeaderViewHolder newCollectionHeaderView(Context context, int groupOrdinal, ViewGroup parent) { 132 | // Create a new view. 133 | View v = LayoutInflater.from(context) 134 | .inflate(R.layout.header_row_item_async, parent, false); 135 | 136 | return new MyHeaderViewHolder(v, groupOrdinal, mAsyncExpandableListView); 137 | } 138 | 139 | @Override 140 | public RecyclerView.ViewHolder newCollectionItemView(Context context, int groupOrdinal, ViewGroup parent) { 141 | // Create a new view. 142 | View v = LayoutInflater.from(context) 143 | .inflate(R.layout.text_row_item_async, parent, false); 144 | 145 | return new MainActivity.NewsItemHolder(v); 146 | } 147 | 148 | @Override 149 | public void bindCollectionHeaderView(Context context, AsyncHeaderViewHolder holder, int groupOrdinal, String headerItem) { 150 | MyHeaderViewHolder myHeaderViewHolder = (MyHeaderViewHolder) holder; 151 | myHeaderViewHolder.getTextView().setText(headerItem); 152 | } 153 | 154 | @Override 155 | public void bindCollectionItemView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, News item) { 156 | MainActivity.NewsItemHolder newsItemHolder = (MainActivity.NewsItemHolder) holder; 157 | newsItemHolder.getTextViewTitle().setText(item.getNewsTitle()); 158 | newsItemHolder.getTextViewDescrption().setText(item.getNewsBody()); 159 | } 160 | 161 | public static class MyHeaderViewHolder extends AsyncHeaderViewHolder implements AsyncExpandableListView.OnGroupStateChangeListener { 162 | 163 | private final TextView textView; 164 | private final ProgressBar mProgressBar; 165 | private ImageView ivExpansionIndicator; 166 | 167 | public MyHeaderViewHolder(View v, int groupOrdinal, AsyncExpandableListView asyncExpandableListView) { 168 | super(v, groupOrdinal, asyncExpandableListView); 169 | textView = (TextView) v.findViewById(R.id.title); 170 | mProgressBar = (ProgressBar) v.findViewById(R.id.progressBar); 171 | mProgressBar.getIndeterminateDrawable().setColorFilter(0xFFFFFFFF, 172 | android.graphics.PorterDuff.Mode.MULTIPLY); 173 | ivExpansionIndicator = (ImageView) v.findViewById(R.id.ivExpansionIndicator); 174 | } 175 | 176 | 177 | public TextView getTextView() { 178 | return textView; 179 | } 180 | 181 | 182 | @Override 183 | public void onGroupStartExpending() { 184 | mProgressBar.setVisibility(View.VISIBLE); 185 | ivExpansionIndicator.setVisibility(View.INVISIBLE); 186 | } 187 | 188 | @Override 189 | public void onGroupExpanded() { 190 | mProgressBar.setVisibility(View.GONE); 191 | ivExpansionIndicator.setVisibility(View.VISIBLE); 192 | ivExpansionIndicator.setImageResource(R.drawable.ic_arrow_up); 193 | } 194 | 195 | @Override 196 | public void onGroupCollapsed() { 197 | mProgressBar.setVisibility(View.GONE); 198 | ivExpansionIndicator.setVisibility(View.VISIBLE); 199 | ivExpansionIndicator.setImageResource(R.drawable.ic_arrow_down); 200 | 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /sampleapp/src/main/java/com/ericliu/asyncexpandablelistsample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelistsample;/* 2 | * Copyright 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.os.Bundle; 22 | import android.support.v7.widget.RecyclerView; 23 | import android.util.Log; 24 | import android.view.LayoutInflater; 25 | import android.view.Menu; 26 | import android.view.MenuInflater; 27 | import android.view.MenuItem; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.TextView; 31 | 32 | import com.ericliu.asyncexpandablelist.CollectionView; 33 | import com.ericliu.asyncexpandablelist.CollectionViewCallbacks; 34 | 35 | 36 | /** 37 | * A simple launcher activity containing a summary sample description, sample log and a custom 38 | * {@link android.support.v4.app.Fragment} which can display a view. 39 | *

40 | * For devices with displays with a width of 720dp or greater, the sample log is always visible, 41 | * on other devices it's visibility is controlled by an item on the Action Bar. 42 | */ 43 | public class MainActivity extends Activity implements CollectionViewCallbacks { 44 | 45 | public static final String TAG = "MainActivity"; 46 | 47 | 48 | private CollectionView mCollectionView; 49 | private CollectionView.Inventory inventory; 50 | private boolean flag; 51 | 52 | @Override 53 | protected void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.activity_main); 56 | 57 | mCollectionView = (CollectionView) findViewById(R.id.collectionView); 58 | mCollectionView.setCollectionCallbacks(this); 59 | 60 | inventory = new CollectionView.Inventory<>(); 61 | 62 | // groupOrdinal dictates the sequence of groups to be displayed in the list, 63 | // the groups will be displayed in an ascending order on groupOrdinal 64 | int groupOrdinal = 0; 65 | CollectionView.InventoryGroup group1 = inventory.newGroup(groupOrdinal); 66 | News news; 67 | 68 | group1.setHeaderItem(getString(R.string.news_header_top_stories)); 69 | news = new News(); 70 | news.setNewsTitle(getString(R.string.news_title1)); 71 | news.setNewsBody(getString(R.string.news_body1)); 72 | group1.addItem(news); 73 | 74 | news = new News(); 75 | news.setNewsTitle(getString(R.string.news_title2)); 76 | news.setNewsBody(getString(R.string.news_body2)); 77 | group1.addItem(news); 78 | 79 | news = new News(); 80 | news.setNewsTitle(getString(R.string.news_title_3)); 81 | news.setNewsBody(getString(R.string.news_body3)); 82 | group1.addItem(news); 83 | 84 | CollectionView.InventoryGroup group2 = inventory.newGroup(2); 85 | group2.setHeaderItem(getString(R.string.news_header_world)); 86 | 87 | news = new News(); 88 | news.setNewsTitle(getString(R.string.news_title4)); 89 | news.setNewsBody(getString(R.string.news_body4)); 90 | group2.addItem(news); 91 | 92 | news = new News(); 93 | news.setNewsTitle(getString(R.string.news_title5)); 94 | news.setNewsBody(getString(R.string.news_body5)); 95 | group2.addItem(news); 96 | 97 | 98 | 99 | CollectionView.InventoryGroup group3 = inventory.newGroup(3); // 2 is smaller than 10, displayed second 100 | group3.setHeaderItem(getString(R.string.news_header_australia)); 101 | 102 | news = new News(); 103 | news.setNewsTitle(getString(R.string.news_title6)); 104 | news.setNewsBody(getString(R.string.news_body6)); 105 | group3.addItem(news); 106 | 107 | news = new News(); 108 | news.setNewsTitle(getString(R.string.news_title7)); 109 | news.setNewsBody(getString(R.string.news_body7)); 110 | group3.addItem(news); 111 | 112 | 113 | news = new News(); 114 | news.setNewsTitle(getString(R.string.news_title8)); 115 | news.setNewsBody(getString(R.string.news_body8)); 116 | group3.addItem(news); 117 | 118 | 119 | mCollectionView.updateInventory(inventory); 120 | 121 | } 122 | 123 | 124 | @Override 125 | public RecyclerView.ViewHolder newCollectionHeaderView(Context context, int groupOrdinal, ViewGroup parent) { 126 | // Create a new view. 127 | View v = LayoutInflater.from(context) 128 | .inflate(R.layout.header_row_item, parent, false); 129 | 130 | return new TitleHolder(v); 131 | } 132 | 133 | @Override 134 | public RecyclerView.ViewHolder newCollectionItemView(Context context, int groupOrdinal, ViewGroup parent) { 135 | // Create a new view. 136 | View v = LayoutInflater.from(context) 137 | .inflate(R.layout.text_row_item, parent, false); 138 | 139 | return new NewsItemHolder(v); 140 | } 141 | 142 | @Override 143 | public void bindCollectionHeaderView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, String headerItem) { 144 | ((TitleHolder) holder).getTextView().setText((String) headerItem); 145 | } 146 | 147 | @Override 148 | public void bindCollectionItemView(Context context, RecyclerView.ViewHolder holder, int groupOrdinal, News item) { 149 | NewsItemHolder newsItemHolder = (NewsItemHolder) holder; 150 | newsItemHolder.getTextViewTitle().setText(item.getNewsTitle()); 151 | newsItemHolder.getTextViewDescrption().setText(item.getNewsBody()); 152 | } 153 | 154 | 155 | public static class TitleHolder extends RecyclerView.ViewHolder { 156 | 157 | private final TextView textView; 158 | 159 | public TitleHolder(View itemView) { 160 | super(itemView); 161 | textView = (TextView) itemView.findViewById(R.id.title); 162 | } 163 | 164 | public TextView getTextView() { 165 | return textView; 166 | } 167 | } 168 | 169 | public static class NewsItemHolder extends RecyclerView.ViewHolder { 170 | 171 | 172 | private final TextView tvTitle; 173 | private final TextView tvDescription; 174 | 175 | public NewsItemHolder(View v) { 176 | super(v); 177 | // Define click listener for the ViewHolder's View. 178 | v.setOnClickListener(new View.OnClickListener() { 179 | @Override 180 | public void onClick(View v) { 181 | Log.d(TAG, "Element " + getPosition() + " clicked."); 182 | } 183 | }); 184 | tvTitle = (TextView) v.findViewById(R.id.title); 185 | tvDescription = (TextView) v.findViewById(R.id.description); 186 | } 187 | 188 | public TextView getTextViewTitle() { 189 | return tvTitle; 190 | } 191 | 192 | public TextView getTextViewDescrption() { 193 | return tvDescription; 194 | } 195 | } 196 | 197 | 198 | @Override 199 | public boolean onCreateOptionsMenu(Menu menu) { 200 | super.onCreateOptionsMenu(menu); 201 | 202 | MenuInflater inflater = getMenuInflater(); 203 | inflater.inflate(R.menu.main, menu); 204 | return true; 205 | } 206 | 207 | @Override 208 | public boolean onOptionsItemSelected(MenuItem item) { 209 | if (item.getItemId() == R.id.launch) { 210 | Intent intent = new Intent(this, AsyncActivity.class); 211 | startActivity(intent); 212 | } 213 | 214 | return super.onOptionsItemSelected(item); 215 | } 216 | } 217 | 218 | -------------------------------------------------------------------------------- /sampleapp/src/main/java/com/ericliu/asyncexpandablelistsample/News.java: -------------------------------------------------------------------------------- 1 | package com.ericliu.asyncexpandablelistsample; 2 | 3 | /** 4 | * Created by ericliu on 12/10/16. 5 | */ 6 | 7 | public class News { 8 | private String newsTitle; 9 | private String newsBody; 10 | 11 | 12 | public String getNewsTitle() { 13 | return newsTitle; 14 | } 15 | 16 | public void setNewsTitle(String newsTitle) { 17 | this.newsTitle = newsTitle; 18 | } 19 | 20 | public String getNewsBody() { 21 | return newsBody; 22 | } 23 | 24 | public void setNewsBody(String newsBody) { 25 | this.newsBody = newsBody; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-hdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-hdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-hdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-hdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-hdpi/tile.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-hdpi/tile.9.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-mdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-mdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-mdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-mdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xhdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xhdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xhdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xhdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xxhdpi/ic_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xxhdpi/ic_arrow_down.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xxhdpi/ic_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xxhdpi/ic_arrow_up.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ericliu001/async-expandable-list/8db3d7f6e45020c950c5877a94c701ec640c3a4c/sampleapp/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/activity_async.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 16 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/header_row_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 32 | 33 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/header_row_item_async.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 31 | 32 | 33 | 36 | 37 | 38 | 52 | 53 | 54 | 65 | 66 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/recycler_view_frag.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 28 | 32 | 36 | 37 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/text_row_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 31 | 32 | 40 | 41 | 49 | 50 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/text_row_item_async.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 31 | 32 | 40 | 41 | 49 | 50 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 |

19 | 22 | 23 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #00BCD4 5 | #00838F 6 | #fff 7 | 8 | #f5f5f5 9 | 10 | #de000000 11 | #8a000000 12 | #60000000 13 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 72dp 20 | 21 | 16dp 22 | 16dp 23 | 24 | 25 | 48dp 26 | 2dp 27 | 0dp 28 | 4dp 29 | 30 | 31 | 32 | 33 | 8dp 34 | 4dp 35 | 16dp 36 | 37 | 4dp 38 | 8dp 39 | 16dp 40 | 32dp 41 | 64dp 42 | 43 | 11sp 44 | 12sp 45 | 13sp 46 | 14sp 47 | 18sp 48 | 20sp 49 | 6sp 50 | 51 | 52 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | AsyncExpandableList 20 | 21 | Element 22 | Grid Layout Manager 23 | Linear Layout Manager 24 | CollectionView 25 | AsyncExpandableListView 26 | Top Stories 27 | Australian Police Arrest 2 Sydney Teens, Seize Knives 28 | SYDNEY - Australian police arrested two teenagers and seized knives in Sydney on Wednesday as the country marked the 14th anniversary of extremist bombings in Indonesia that killed 202, including 88 Australians, police said. 29 | Queensland report outlines 50 per cent renewable energy map 30 | The security of Queensland\'s power supply won\'t be undermined by a government target of 50 per cent renewable energy by 2030, Energy Minister Mark Bailey says. 31 | VB and sheep farms, Lee brings the laughs 32 | VB beer, barbecues and sheep farms - they\'re not the kind of things you expect to hear from a visiting leader in a formal speech to federal parliament. 33 | World 34 | \'Sanctions brought nothing\': German politicians call for rapprochement with Russia 35 | The current policy of “saber rattling” should not continue, Erwin Sellering, the prime minister of the German state of Mecklenburg-Western Pomerania, told Germany\'s weekly Welt am Sonntag newspaper, as he called for lifting anti-Russian sanctions. 36 | Thai prince to visit ailing king, hospital says as health fears grow 37 | Thai King Bhumibol Adulyadej attends a parade to mark his 81st birthday in Bangkok, Thailand, 02 December 2008. (AAP ). Previous Next Show Grid. 38 | Australia 39 | \'I beg you, do not just put it in the filing cabinet\', witness pleads at NT Royal Commission 40 | The survival of Indigenous people in the Northern Territory depended on the outcome of the Royal Commission into Child Protection and Detention, a witness told the commission hearing in Darwin. 41 | HSC 2016: 77000 students to sit first exams across NSW 42 | More than 77,000 NSW high school students will sit their first HSC exams this week as one of the final cohorts to sit the test before the NSW government enacts sweeping reforms across the state. 43 | Lawyers meet voluntary pro bono target for first time since 2013 44 | A voluntary target for the amount of pro bono work done by Australian lawyers has been met for the first time since 2013. Key points: The Australian Pro Bono Centre\'s asks lawyers to do 35 hours of free community work a year; Pro bono services can help ...\n 45 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':asyncexpandablelist', ':sampleapp' --------------------------------------------------------------------------------