├── .buildscript ├── findbugs.gradle └── maven-push.gradle ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── adaptercommands ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hannesdorfmann │ │ │ └── adaptercommands │ │ │ ├── AdapterCommandProcessor.java │ │ │ ├── ItemChangedDetector.java │ │ │ └── command │ │ │ ├── AdapterCommand.java │ │ │ ├── DiffCommandsCalculator.java │ │ │ ├── EntireDataSetChangedCommand.java │ │ │ ├── ItemChangedCommand.java │ │ │ ├── ItemInsertedCommand.java │ │ │ ├── ItemMovedCommand.java │ │ │ ├── ItemRangeChangedCommand.java │ │ │ ├── ItemRangeInsertedCommand.java │ │ │ ├── ItemRangeRemovedCommand.java │ │ │ ├── ItemRemovedCommand.java │ │ │ └── ThreadSafeDiffCommandsCalculator.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── hannesdorfmann │ └── adaptercommands │ ├── AdapterCommandProcessorTest.java │ └── command │ ├── CommandsTest.java │ └── DiffCommandsCalculatorTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hannesdorfmann │ │ └── adaptercommands │ │ └── example │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hannesdorfmann │ │ │ └── adaptercommands │ │ │ └── example │ │ │ ├── Item.java │ │ │ ├── ItemAdapter.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-land │ │ └── integers.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── hannesdorfmann │ └── adaptercommands │ └── example │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.buildscript/findbugs.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | apply plugin: 'findbugs' 18 | 19 | afterEvaluate { 20 | def variants = plugins.hasPlugin('com.android.application') ? 21 | android.applicationVariants : android.libraryVariants 22 | 23 | variants.each { variant -> 24 | def task = tasks.create("findBugs${variant.name.capitalize()}", FindBugs) 25 | 26 | task.group = 'verification' 27 | task.description = "Run FindBugs for the ${variant.description}." 28 | 29 | task.effort = 'max' 30 | 31 | task.reportLevel = 'high' 32 | task.reports { 33 | xml { 34 | enabled = false 35 | } 36 | html { 37 | enabled = true 38 | } 39 | } 40 | 41 | def variantCompile = variant.javaCompile 42 | 43 | task.classes = fileTree(variantCompile.destinationDir) 44 | task.source = variantCompile.source 45 | task.classpath = variantCompile.classpath.plus(project.files(android.bootClasspath)) 46 | 47 | task.dependsOn(variantCompile) 48 | tasks.getByName('check').dependsOn(task) 49 | } 50 | } -------------------------------------------------------------------------------- /.buildscript/maven-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : 26 | "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : 31 | "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | /* 48 | pom.withXml { 49 | asNode().children().first().parent() 50 | .appendNode('repositories') 51 | .appendNode('repository') 52 | .appendNode('id', 'clojars').parent() 53 | .appendNode('url', 'https://clojars.org/repo/') 54 | } 55 | */ 56 | 57 | pom.groupId = GROUP 58 | pom.artifactId = POM_ARTIFACT_ID 59 | pom.version = VERSION_NAME 60 | 61 | repository(url: getReleaseRepositoryUrl()) { 62 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 63 | } 64 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 65 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 66 | } 67 | 68 | pom.project { 69 | name POM_NAME 70 | packaging POM_PACKAGING 71 | description POM_DESCRIPTION 72 | url POM_URL 73 | 74 | scm { 75 | url POM_SCM_URL 76 | connection POM_SCM_CONNECTION 77 | developerConnection POM_SCM_DEV_CONNECTION 78 | } 79 | 80 | licenses { 81 | license { 82 | name POM_LICENCE_NAME 83 | url POM_LICENCE_URL 84 | distribution POM_LICENCE_DIST 85 | } 86 | } 87 | 88 | developers { 89 | developer { 90 | id POM_DEVELOPER_ID 91 | name POM_DEVELOPER_NAME 92 | } 93 | } 94 | } 95 | 96 | 97 | // Resolve dependencies to other modules 98 | pom.whenConfigured { pom -> 99 | pom.dependencies.findAll { dep -> dep.groupId == rootProject.name }.collect { dep -> 100 | dep.groupId = pom.groupId = project.GROUP 101 | dep.version = pom.version = project.VERSION_NAME 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | 109 | signing { 110 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 111 | sign configurations.archives 112 | } 113 | 114 | task androidJavadocs(type: Javadoc) { 115 | source = android.sourceSets.main.java.srcDirs 116 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 117 | if (JavaVersion.current().isJava8Compatible()) { 118 | allprojects { 119 | tasks.withType(Javadoc) { 120 | options.addStringOption('Xdoclint:none', '-quiet') 121 | } 122 | } 123 | } 124 | } 125 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 126 | classifier = 'javadoc' 127 | from androidJavadocs.destinationDir 128 | } 129 | task androidSourcesJar(type: Jar) { 130 | classifier = 'sources' 131 | from android.sourceSets.main.java.sourceFiles 132 | } 133 | artifacts { 134 | archives androidSourcesJar 135 | archives androidJavadocsJar 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | 10 | 11 | # Built application files 12 | *.apk 13 | *.ap_ 14 | bin/ 15 | gen/ 16 | classes/ 17 | gen-external-apklibs/ 18 | 19 | # Eclipse project files 20 | .classpath 21 | .project 22 | .metadata 23 | .settings 24 | 25 | # IntelliJ files 26 | .idea 27 | *.iml 28 | 29 | # OSX files 30 | .DS_Store 31 | 32 | # Windows files 33 | Thumbs.db 34 | 35 | # vi swap files 36 | *.swp 37 | 38 | # backup files 39 | *.bak 40 | 41 | 42 | # Files for the Dalvik VM 43 | *.dex 44 | 45 | # Java class files 46 | *.class 47 | 48 | # Generated files 49 | bin/ 50 | gen/ 51 | 52 | # Gradle files 53 | .gradle/ 54 | build/ 55 | .gradle 56 | 57 | #maven files 58 | target/ 59 | /null 60 | 61 | # Local configuration file (sdk path, etc) 62 | 63 | local.properties 64 | 65 | # Proguard folder generated by Eclipse 66 | proguard/ 67 | 68 | #Log Files 69 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | android: 4 | components: 5 | - platform-tools 6 | - tools 7 | - android-23 8 | - build-tools-23.0.3 9 | - extra-android-support 10 | - extra-android-m2repository 11 | 12 | jdk: 13 | - oraclejdk8 14 | 15 | #after_success: 16 | # - .buildscript/deploy_snapshot.sh 17 | 18 | #env: 19 | # global: 20 | # - secure: "NIWC0zkThskXn7uduTJ1yT78voqEgzEfw8tOImGNBjZ/NDU6yxM4bh+tq+fnkn5ENjELV6fgcYd2DUJSWmkFD2k9ZMRNLm//AqlQihl8aT+DpWhDdCkQjnolHnjm1O7+ys7Q/vswBZEzkBxzIgivajZEzvjarQItJjbpBftQ0Cs=" 21 | # - secure: "ahPT9EzJVpkM4q2HA/VBxUzgicvfdOOZaEvOiQKJofy1FrLjrBS2LFxqCbyffg0sjGUyvBMLg767CSt/0xRRFWIpsjxCfmvEmAURi89zdZ8MUNXIwe7x/0lXCdQIt8eueq3Qh5qFwJUy4aFbzVvcmMXKswWzw1O0+IcvYX00/xc=" 22 | 23 | branches: 24 | except: 25 | - gh-pages 26 | 27 | notifications: 28 | email: false 29 | 30 | sudo: false 31 | 32 | script: ./gradlew build 33 | 34 | cache: 35 | directories: 36 | - $HOME/.gradle 37 | 38 | # before_script: 39 | # - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a 40 | # - emulator -avd test -no-skin -no-audio -no-window & 41 | # - android-wait-for-emulator 42 | # - adb shell input keyevent 82 & 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Hannes Dorfmann 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | This library is deprecated. Use [DiffUtils](https://developer.android.com/reference/android/support/v7/util/DiffUtil.html) from Android RecyclerView library which does exactly the same as AdapterCommands. AdapterCommands has been developed and released before DiffUtils has been released, however, now that Google has published and is maintaining DiffUtils there is very little reason to prefer this library over DiffUtils. 3 | 4 | # AdapterCommands 5 | Drop in solution to animate RecyclerView's dataset changes by using the `command pattern` for adapters with **not stable ids**. 6 | Read my [blog post](http://hannesdorfmann.com/android/adapter-commands) for more information. 7 | 8 | Keep in mind that the runtime of `DiffCommandsCalculator` is `O(n*m)` (n = number of items in old list, m = number of items in new list). 9 | So you better run this on a background thread if your data set contains many items. 10 | 11 | ##Dependencies 12 | 13 | ```groovy 14 | compile 'com.hannesdorfmann.adaptercommands:adaptercommands:1.0.4' 15 | ``` 16 | 17 | ## How to use 18 | There are basically 2 components: 19 | - `DiffCommandsCalculator` that calculates the difference from previous data set to the new data set and returns `List`. Please note that `DiffCommandsCalculator` is **not thread safe**. If you need a thread safe instance use `ThreadSafeDiffCommandsCalculator`. 20 | - `AdapterCommandProcessor` takes `List` and executes each command to trigger RecyclerView's `ItemAnimator` to run animations. 21 | 22 | ```java 23 | public class MainActivity extends AppCompatActivity { 24 | 25 | @Bind(R.id.recyclerView) RecyclerView recyclerView; 26 | 27 | List items = new ArrayList(); 28 | Random random = new Random(); 29 | ItemAdapter adapter; // RecyclerView adapter 30 | AdapterCommandProcessor commandProcessor; 31 | DiffCommandsCalculator commandsCalculator = new DiffCommandsCalculator(); 32 | 33 | @Override protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | ButterKnife.bind(this); 37 | 38 | adapter = new ItemAdapter(this, items); 39 | recyclerView.setAdapter(adapter); 40 | recyclerView.setLayoutManager(new GridLayoutManager(this, 4)); 41 | 42 | commandProcessor = new AdapterCommandProcessor(adapter); 43 | } 44 | 45 | // Called when new items should be displayed in RecyclerView 46 | public void setItems(List newItems){ 47 | adapter.setItems(newItems); 48 | List commands = commandsCalculator.diff(newItems); 49 | commandProcessor.execute(commands); // executes commands that triggers animations 50 | } 51 | ``` 52 | 53 | ## MVP 54 | Best practise is to use a `PresentationModel` and `Model-View-Presenter`. See my [blog post](http://hannesdorfmann.com/android/adapter-commands) for a concrete example. 55 | 56 | ## Customization 57 | - comparing items 58 | `DiffCommandsCalculator` uses standard java's `equals()` method to compare two items (one from old list, one from new list). 59 | So you have to override `equals()` and `hashCode()` in your model class (use IDE to generate that): 60 | ```java 61 | public class Item { 62 | 63 | int id; 64 | String text; 65 | 66 | @Override public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | Item item = (Item) o; 71 | 72 | return id == item.id; 73 | } 74 | 75 | @Override public int hashCode() { 76 | return id; 77 | } 78 | ``` 79 | As you might have noticed, we only use `Item.id` for equals. The reason is that if we have this item in old list `item { id = 1, text ="Foo"}` and the same item with the same id in the new list `{item { id = 1, text ="other"}` we just want to compare this items by the id. 80 | What here has happened was that the `item.text` has been changed from new list to old list, but we still compare two items just by `item.id` since this is the property we use in `equals()`. 81 | 82 | However, when a `item.text` has been changed, we have to detect this too because we have to call `adapter.notifyItemChanged(position)` (`ItemChangedCommand`). 83 | So we can provide an `ItemChangedDetector` that we can pass as constructor argument to `DiffCommandsCalculator`: 84 | 85 | ```java 86 | class MyItemChangedDetector implements ItemChangedDetector() { 87 | @Override public boolean hasChanged(Item oldItem, Item newItem) { 88 | return !oldItem.text.equals(newItem.text); 89 | } 90 | }; 91 | ``` 92 | and then use it like this: 93 | ```java 94 | DiffCommandsCalculator calculator = new DiffCommandsCalculator<>(new MyItemChangedDetector()); 95 | ``` 96 | 97 | - We also can specify what exactly should happen on the first time we use `DiffCommandsCalculator` (there is no old list to compare to). 98 | In this case we either could call `adapter.notifyDatasetChanged()` (`EntireDatasetChangedCommand`) which is the default behaviour or `adapter.notifyItemRangeInserted(0, items.size())` (`ItemRangeInsertedCommand`) which then will run `ItemAnimator` so that items will animate in. 99 | You can specify the behaviour as constructor parameter `DiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff)`: `new DiffCommandsCalculator(false)` uses `EntireDatasetChangedCommand` (no animations, equivalent to `new DiffCommandsCalculator()`) whereas `new DiffCommandsCalculator(true)` uses `ItemRangeInsertedCommand` (animations). 100 | 101 | 102 | ## License 103 | ``` 104 | Copyright 2016 Hannes Dorfmann 105 | 106 | Licensed under the Apache License, Version 2.0 (the "License"); 107 | you may not use this file except in compliance with the License. 108 | You may obtain a copy of the License at 109 | 110 | http://www.apache.org/licenses/LICENSE-2.0 111 | 112 | Unless required by applicable law or agreed to in writing, software 113 | distributed under the License is distributed on an "AS IS" BASIS, 114 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 115 | See the License for the specific language governing permissions and 116 | limitations under the License. 117 | ``` 118 | -------------------------------------------------------------------------------- /adaptercommands/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | bin/ 7 | gen/ 8 | classes/ 9 | gen-external-apklibs/ 10 | 11 | # Eclipse project files 12 | .classpath 13 | .project 14 | .metadata 15 | .settings 16 | 17 | # IntelliJ files 18 | .idea 19 | *.iml 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Windows files 25 | Thumbs.db 26 | 27 | # vi swap files 28 | *.swp 29 | 30 | # backup files 31 | *.bak 32 | 33 | 34 | # Files for the Dalvik VM 35 | *.dex 36 | 37 | # Java class files 38 | *.class 39 | 40 | # Generated files 41 | bin/ 42 | gen/ 43 | 44 | # Gradle files 45 | .gradle/ 46 | build/ 47 | .gradle 48 | 49 | #maven files 50 | target/ 51 | /null 52 | 53 | # Local configuration file (sdk path, etc) 54 | 55 | local.properties 56 | 57 | # Proguard folder generated by Eclipse 58 | proguard/ 59 | 60 | #Log Files 61 | *.log -------------------------------------------------------------------------------- /adaptercommands/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | apply plugin: 'com.android.library' 18 | apply from: '../.buildscript/maven-push.gradle' 19 | apply from: '../.buildscript/findbugs.gradle' 20 | 21 | android { 22 | compileSdkVersion 23 23 | buildToolsVersion "23.0.3" 24 | 25 | defaultConfig { 26 | minSdkVersion 14 27 | targetSdkVersion 23 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | compile 'com.android.support:recyclerview-v7:'+rootProject.ext.recyclerviewVersion 42 | 43 | 44 | testCompile 'junit:junit:' + rootProject.ext.junitVersion 45 | testCompile 'org.mockito:mockito-core:' + rootProject.ext.mockitoVersion 46 | 47 | 48 | testCompile('org.powermock:powermock-api-mockito:' + rootProject.ext.powermockVersion) { 49 | exclude module: 'hamcrest-core' 50 | exclude module: 'objenesis' 51 | } 52 | testCompile('org.powermock:powermock-module-junit4:' + rootProject.ext.powermockVersion) { 53 | exclude module: 'hamcrest-core' 54 | exclude module: 'objenesis' 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /adaptercommands/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016 Hannes Dorfmann 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 | POM_NAME = Adapter-Commands 18 | POM_ARTIFACT_ID = adaptercommands 19 | POM_PACKAGING = aar -------------------------------------------------------------------------------- /adaptercommands/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/hannes/android-sdks/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 | -------------------------------------------------------------------------------- /adaptercommands/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/AdapterCommandProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | import com.hannesdorfmann.adaptercommands.command.AdapterCommand; 23 | import java.util.List; 24 | 25 | /** 26 | * Executes a list of {@link AdapterCommand}s by calling {@link #execute(List)} 27 | * 28 | * @author Hannes Dorfmann 29 | * @since 1.0 30 | */ 31 | public class AdapterCommandProcessor { 32 | 33 | private final RecyclerView.Adapter adapter; 34 | 35 | public AdapterCommandProcessor(@NonNull RecyclerView.Adapter adapter) { 36 | if (adapter == null) { 37 | throw new NullPointerException("adapter == null"); 38 | } 39 | 40 | this.adapter = adapter; 41 | } 42 | 43 | /** 44 | * Executes the passed list of adapter commands by calling {@link AdapterCommand#execute(RecyclerView.Adapter)} 45 | * on each command in the list 46 | * 47 | * @param commands The comands to execute 48 | */ 49 | @MainThread public void execute(@NonNull List commands) { 50 | if (commands == null) { 51 | throw new NullPointerException("commands == null"); 52 | } 53 | for (int i = 0; i < commands.size(); i++) { 54 | commands.get(i).execute(adapter); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/ItemChangedDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands; 18 | 19 | /** 20 | * Responsible to determine whether an item has been changed or not. 21 | * 22 | * @author Hannes Dorfmann 23 | * @since 1.0 24 | */ 25 | public interface ItemChangedDetector { 26 | 27 | /** 28 | * Determinew whether an item has been changed or not by comparing the oldItem with the newItem 29 | * 30 | * @param oldItem The old item 31 | * @param newItem the new item 32 | * @return true if changed, otherwise false 33 | */ 34 | public boolean hasChanged(T oldItem, T newItem); 35 | } 36 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/AdapterCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.v7.widget.RecyclerView; 21 | import com.hannesdorfmann.adaptercommands.AdapterCommandProcessor; 22 | 23 | /** 24 | * This interface represents a Command. This command will be executed by a {@link 25 | * AdapterCommandProcessor} to notify the adapter about changes in the dataset and to kick in the 26 | * item animator by invoking corresponding adapters notify() method (like notifyItemInsterted()) 27 | * 28 | * @author Hannes Dorfmann 29 | * @since 1.0 30 | */ 31 | public interface AdapterCommand { 32 | 33 | /** 34 | * Executes this command by calling the corresponding method on the given {@link 35 | * RecyclerView.Adapter} 36 | * 37 | * @param adapter The adapter 38 | */ 39 | @MainThread public void execute(RecyclerView.Adapter adapter); 40 | } 41 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/DiffCommandsCalculator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.NonNull; 20 | import android.support.annotation.Nullable; 21 | import com.hannesdorfmann.adaptercommands.ItemChangedDetector; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | /** 27 | * This class is responsible to calculate the difference between two lists and returns a list of 28 | * {@link AdapterCommand} that can be executed to enable RecyclerView animations. 29 | * 30 | *

31 | * This class is not thread safe! If you need a thread safe instance use {@link 32 | * ThreadSafeDiffCommandsCalculator} 33 | *

34 | * 35 | * @author Hannes Dorfmann 36 | * @since 1.0 37 | */ 38 | public class DiffCommandsCalculator { 39 | 40 | private final boolean itemRangeInsertedOnFirstDiff; 41 | private List oldList; 42 | private final ItemChangedDetector detector; 43 | 44 | /** 45 | * Default constructor. Uses {@link EntireDataSetChangedCommand} as resulting command on first 46 | * time {@link #diff(List)}. This can be changed by using {@link #DiffCommandsCalculator(boolean)} 47 | * constructor 48 | * 49 | * @see #DiffCommandsCalculator(boolean) 50 | * @see #DiffCommandsCalculator(boolean, ItemChangedDetector) 51 | */ 52 | public DiffCommandsCalculator() { 53 | this(false, null); 54 | } 55 | 56 | /** 57 | * This constructor allows you to specify the resulting command on first time {@link 58 | * #diff(List)}. 59 | * 60 | * @param itemRangeInsertedOnFirstDiff if true {@link ItemRangeInsertedCommand} will be 61 | * used which cause a RecyclerView item animations. Use false if {@link 62 | * EntireDataSetChangedCommand} should be used (no RecyclerView item animations). 63 | */ 64 | public DiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff) { 65 | this(itemRangeInsertedOnFirstDiff, null); 66 | } 67 | 68 | /** 69 | * Constructs a new instance with an {@link ItemChangedDetector} 70 | * 71 | * @param detector that is responsible to determine whether an item has been changed (internal 72 | * data changed) or not 73 | */ 74 | public DiffCommandsCalculator(ItemChangedDetector detector) { 75 | this(false, detector); 76 | } 77 | 78 | /** 79 | * Creates a new instance. 80 | * 81 | * @param itemRangeInsertedOnFirstDiff if true {@link ItemRangeInsertedCommand} will be 82 | * used which cause a RecyclerView item animations. Use false if {@link 83 | * EntireDataSetChangedCommand} should be used (no RecyclerView item animations). 84 | * @param detector that is responsible to determine whether an item has been changed (internal 85 | * data changed or not) 86 | */ 87 | public DiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff, 88 | @Nullable ItemChangedDetector detector) { 89 | this.itemRangeInsertedOnFirstDiff = itemRangeInsertedOnFirstDiff; 90 | this.detector = detector; 91 | } 92 | 93 | /** 94 | * This method calculates the difference of previous list of items and the new list. 95 | * This method is not thread safe. 96 | * 97 | * @param newList The new items that we use to calculate the difference 98 | * @return List of commands 99 | * @see ThreadSafeDiffCommandsCalculator 100 | */ 101 | public List diff(@NonNull List newList) { 102 | 103 | if (newList == null) { 104 | throw new NullPointerException("newList == null"); 105 | } 106 | 107 | int newSize = newList.size(); 108 | // first time called 109 | if (oldList == null) { 110 | oldList = new ArrayList<>(); 111 | oldList.addAll(newList); 112 | 113 | List commands = new ArrayList<>(1); 114 | 115 | if (newSize == 0 || !itemRangeInsertedOnFirstDiff) { 116 | commands.add(new EntireDataSetChangedCommand()); 117 | } else { 118 | commands.add(new ItemRangeInsertedCommand(0, newSize)); 119 | } 120 | return commands; 121 | } 122 | 123 | // new list empty 124 | if (newList.isEmpty()) { 125 | if (oldList.isEmpty()){ 126 | return Collections.emptyList(); 127 | } 128 | List commands = new ArrayList<>(1); 129 | commands.add(new ItemRangeRemovedCommand(0, oldList.size())); 130 | oldList.clear(); // for next call 131 | return commands; 132 | } 133 | 134 | List commands = new ArrayList<>(newSize); 135 | 136 | int M = oldList.size(); 137 | int N = newList.size(); 138 | 139 | // opt[i][j] = length of LCS of oldList[i..M] and y[j..N] 140 | int[][] opt = new int[M + 1][N + 1]; 141 | 142 | // compute length of LCS and all subproblems via dynamic programming 143 | for (int i = M - 1; i >= 0; i--) { 144 | for (int j = N - 1; j >= 0; j--) { 145 | if (oldList.get(i).equals(newList.get(j))) { 146 | opt[i][j] = opt[i + 1][j + 1] + 1; 147 | } else { 148 | opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]); 149 | } 150 | } 151 | } 152 | 153 | // LinkedHashMap insertCommands = new LinkedHashMap<>(); 154 | // LinkedHashMap removeCommands = new LinkedHashMap<>(); 155 | 156 | int insertRemoveOffset = 0; 157 | // recover LCS itself and print out non-matching lines to standard output 158 | int i = 0, j = 0; 159 | while (i < M && j < N) { 160 | T oldItem = oldList.get(i); 161 | T newItem = newList.get(j); 162 | if (oldItem.equals(newItem)) { 163 | if (detector != null && detector.hasChanged(oldItem, newItem)) { 164 | commands.add(new ItemChangedCommand(j)); 165 | } 166 | i++; 167 | j++; 168 | } else if (opt[i + 1][j] >= opt[i][j + 1]) { 169 | //T item = oldList.get(i); 170 | //handleRemoveCommand(item, i + insertRemoveOffset, insertCommands, removeCommands, commands); 171 | commands.add(new ItemRemovedCommand(i + insertRemoveOffset)); 172 | insertRemoveOffset--; 173 | i++; 174 | } else { 175 | //T item = newList.get(j); 176 | //handleInsertCommand(item, j, insertCommands, removeCommands, commands); 177 | commands.add(new ItemInsertedCommand(j)); 178 | insertRemoveOffset++; 179 | j++; 180 | } 181 | } 182 | 183 | // dump out one remainder of one string if the other is exhausted 184 | while (i < M || j < N) { 185 | if (i == M) { 186 | // T item = newList.get(j); 187 | // handleInsertCommand(item, j, insertCommands, removeCommands, commands); 188 | commands.add(new ItemInsertedCommand(j)); 189 | insertRemoveOffset++; 190 | j++; 191 | } else if (j == N) { 192 | // T item = oldList.get(i); 193 | // handleRemoveCommand(item, i + insertRemoveOffset, insertCommands, removeCommands, commands); 194 | commands.add(new ItemRemovedCommand(i + insertRemoveOffset)); 195 | insertRemoveOffset--; 196 | i++; 197 | } 198 | } 199 | 200 | oldList.clear(); 201 | oldList.addAll(newList); 202 | 203 | // TODO batch commands (see batching branch). 204 | // TODO move commands (see handleRemoveCommand() methods etc.) 205 | 206 | return commands; 207 | } 208 | 209 | /* 210 | private void handleRemoveCommand(T item, int removePosition, 211 | Map insertCommands, Map removeCommands, 212 | List commands) { 213 | 214 | //ItemInsertedCommand iCommand = insertCommands.get(item); 215 | ItemInsertedCommand iCommand = null; 216 | if (iCommand != null) { 217 | ItemMovedCommand mCommand = new ItemMovedCommand(removePosition, iCommand.position); 218 | commands.remove(iCommand); 219 | for (int i = 0; i < commands.size(); i++) { 220 | if (iCommand == commands.get(i)) { 221 | commands.set(i, mCommand); 222 | break; 223 | } 224 | } 225 | insertCommands.remove(item); 226 | Log.d("Items", 227 | "Alg: Move item (" + item + ") from " + removePosition + " to " + iCommand.position); 228 | } else { 229 | ItemRemovedCommand rCommand = new ItemRemovedCommand(removePosition); 230 | removeCommands.put(item, rCommand); 231 | commands.add(rCommand); 232 | Log.d("Items", "Alg: removed item (" + item + ") at position " + removePosition); 233 | } 234 | } 235 | 236 | private void handleInsertCommand(T item, int insertPosition, 237 | Map insertCommands, Map removeCommands, 238 | List commands) { 239 | 240 | // ItemRemovedCommand rCommand = removeCommands.get(item); 241 | ItemRemovedCommand rCommand = null; 242 | if (rCommand != null) { 243 | insertCommands.remove(item); 244 | ItemMovedCommand mCommand = new ItemMovedCommand(rCommand.position, insertPosition); 245 | for (int i = 0; i < commands.size(); i++) { 246 | if (rCommand == commands.get(i)) { 247 | commands.set(i, mCommand); 248 | break; 249 | } 250 | } 251 | commands.remove(rCommand); 252 | Log.d("Items", 253 | "Alg: Move item (" + item + ") from " + rCommand.position + " to " + insertPosition); 254 | } else { 255 | ItemInsertedCommand iCommand = new ItemInsertedCommand(insertPosition); 256 | insertCommands.put(item, iCommand); 257 | commands.add(iCommand); 258 | Log.d("Items", "Alg: insert item (" + item + ") at position " + insertPosition); 259 | } 260 | } 261 | */ 262 | } 263 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/EntireDataSetChangedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyDataSetChanged()} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class EntireDataSetChangedCommand implements AdapterCommand { 30 | 31 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 32 | adapter.notifyDataSetChanged(); 33 | } 34 | 35 | @Override public String toString() { 36 | return "EntireDataSetChangedCommand{}"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemChangedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemChanged(int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemChangedCommand implements AdapterCommand { 30 | 31 | final int position; 32 | 33 | public ItemChangedCommand(int position) { 34 | if (position < 0) { 35 | throw new IllegalArgumentException("position < 0"); 36 | } 37 | 38 | this.position = position; 39 | } 40 | 41 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 42 | adapter.notifyItemChanged(position); 43 | } 44 | 45 | @Override public String toString() { 46 | return "ItemChangedCommand{" + 47 | "position=" + position + 48 | '}'; 49 | } 50 | 51 | @Override public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | 55 | ItemChangedCommand that = (ItemChangedCommand) o; 56 | 57 | return position == that.position; 58 | } 59 | 60 | @Override public int hashCode() { 61 | return position; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemInsertedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemInserted(int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemInsertedCommand implements AdapterCommand { 30 | 31 | final int position; 32 | 33 | public ItemInsertedCommand(int position) { 34 | if (position < 0) { 35 | throw new IllegalArgumentException("position < 0"); 36 | } 37 | 38 | this.position = position; 39 | } 40 | 41 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 42 | adapter.notifyItemInserted(position); 43 | } 44 | 45 | @Override public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | 49 | ItemInsertedCommand that = (ItemInsertedCommand) o; 50 | 51 | return position == that.position; 52 | } 53 | 54 | @Override public int hashCode() { 55 | return position; 56 | } 57 | 58 | @Override public String toString() { 59 | return "ItemInsertedCommand{" + 60 | "position=" + position + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemMovedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemMoved(int, int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemMovedCommand implements AdapterCommand { 30 | 31 | final int fromPosition; 32 | final int toPosition; 33 | 34 | public ItemMovedCommand(int fromPosition, int toPosition) { 35 | if (fromPosition < 0) { 36 | throw new IllegalArgumentException("fromPosition < 0"); 37 | } 38 | if (toPosition < 0) { 39 | throw new IllegalArgumentException("toPosition < 0"); 40 | } 41 | 42 | this.fromPosition = fromPosition; 43 | this.toPosition = toPosition; 44 | } 45 | 46 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 47 | adapter.notifyItemMoved(fromPosition, toPosition); 48 | } 49 | 50 | @Override public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | 54 | ItemMovedCommand that = (ItemMovedCommand) o; 55 | 56 | if (fromPosition != that.fromPosition) return false; 57 | return toPosition == that.toPosition; 58 | } 59 | 60 | @Override public int hashCode() { 61 | int result = fromPosition; 62 | result = 31 * result + toPosition; 63 | return result; 64 | } 65 | 66 | @Override public String toString() { 67 | return "ItemMovedCommand{" + 68 | "fromPosition=" + fromPosition + 69 | ", toPosition=" + toPosition + 70 | '}'; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemRangeChangedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemRangeChanged(int, int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemRangeChangedCommand implements AdapterCommand { 30 | 31 | final int startPosition; 32 | final int itemCount; 33 | 34 | public ItemRangeChangedCommand(int startPosition, int itemCount) { 35 | if (startPosition < 0) { 36 | throw new IllegalArgumentException("startPosition < 0"); 37 | } 38 | 39 | if (itemCount <= 0) { 40 | throw new IllegalArgumentException("itemCount <= 0"); 41 | } 42 | 43 | this.startPosition = startPosition; 44 | this.itemCount = itemCount; 45 | } 46 | 47 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 48 | adapter.notifyItemRangeChanged(startPosition, itemCount); 49 | } 50 | 51 | @Override public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | 55 | ItemRangeChangedCommand that = (ItemRangeChangedCommand) o; 56 | 57 | if (startPosition != that.startPosition) return false; 58 | return itemCount == that.itemCount; 59 | } 60 | 61 | @Override public int hashCode() { 62 | int result = startPosition; 63 | result = 31 * result + itemCount; 64 | return result; 65 | } 66 | 67 | @Override public String toString() { 68 | return "ItemRangeChangedCommand{" + 69 | "startPosition=" + startPosition + 70 | ", itemCount=" + itemCount + 71 | '}'; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemRangeInsertedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemRangeInserted(int, int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemRangeInsertedCommand implements AdapterCommand { 30 | 31 | final int startPosition; 32 | final int itemCount; 33 | 34 | public ItemRangeInsertedCommand(int startPosition, int itemCount) { 35 | if (startPosition < 0) { 36 | throw new IllegalArgumentException("startPosition < 0"); 37 | } 38 | 39 | if (itemCount <= 0) { 40 | throw new IllegalArgumentException("itemCount <= 0"); 41 | } 42 | 43 | this.startPosition = startPosition; 44 | this.itemCount = itemCount; 45 | } 46 | 47 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 48 | adapter.notifyItemRangeInserted(startPosition, itemCount); 49 | } 50 | 51 | @Override public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | 55 | ItemRangeInsertedCommand that = (ItemRangeInsertedCommand) o; 56 | 57 | if (startPosition != that.startPosition) return false; 58 | return itemCount == that.itemCount; 59 | } 60 | 61 | @Override public int hashCode() { 62 | int result = startPosition; 63 | result = 31 * result + itemCount; 64 | return result; 65 | } 66 | 67 | @Override public String toString() { 68 | return "ItemRangeInsertedCommand{" + 69 | "startPosition=" + startPosition + 70 | ", itemCount=" + itemCount + 71 | '}'; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemRangeRemovedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemRangeRemoved(int, int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemRangeRemovedCommand implements AdapterCommand { 30 | 31 | final int startPosition; 32 | final int itemCount; 33 | 34 | public ItemRangeRemovedCommand(int startPosition, int itemCount) { 35 | if (startPosition < 0) { 36 | throw new IllegalArgumentException("startPosition < 0"); 37 | } 38 | 39 | if (itemCount <= 0){ 40 | throw new IllegalArgumentException("itemCount <= 0"); 41 | } 42 | 43 | this.startPosition = startPosition; 44 | this.itemCount = itemCount; 45 | } 46 | 47 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 48 | adapter.notifyItemRangeRemoved(startPosition, itemCount); 49 | } 50 | 51 | @Override public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | 55 | ItemRangeRemovedCommand that = (ItemRangeRemovedCommand) o; 56 | 57 | if (startPosition != that.startPosition) return false; 58 | return itemCount == that.itemCount; 59 | } 60 | 61 | @Override public int hashCode() { 62 | int result = startPosition; 63 | result = 31 * result + itemCount; 64 | return result; 65 | } 66 | 67 | @Override public String toString() { 68 | return "ItemRangeRemovedCommand{" + 69 | "startPosition=" + startPosition + 70 | ", itemCount=" + itemCount + 71 | '}'; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ItemRemovedCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.MainThread; 20 | import android.support.annotation.NonNull; 21 | import android.support.v7.widget.RecyclerView; 22 | 23 | /** 24 | * This command simply calls {@link RecyclerView.Adapter#notifyItemRemoved(int)} (int)} 25 | * 26 | * @author Hannes Dorfmann 27 | * @since 1.0 28 | */ 29 | public class ItemRemovedCommand implements AdapterCommand { 30 | 31 | final int position; 32 | 33 | public ItemRemovedCommand(int position) { 34 | if (position < 0) { 35 | throw new IllegalArgumentException("position < 0"); 36 | } 37 | 38 | this.position = position; 39 | } 40 | 41 | @MainThread @Override public void execute(@NonNull RecyclerView.Adapter adapter) { 42 | adapter.notifyItemRemoved(position); 43 | } 44 | 45 | @Override public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | 49 | ItemRemovedCommand that = (ItemRemovedCommand) o; 50 | 51 | return position == that.position; 52 | } 53 | 54 | @Override public int hashCode() { 55 | return position; 56 | } 57 | 58 | @Override public String toString() { 59 | return "ItemRemovedCommand{" + 60 | "position=" + position + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /adaptercommands/src/main/java/com/hannesdorfmann/adaptercommands/command/ThreadSafeDiffCommandsCalculator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.annotation.NonNull; 20 | import android.support.annotation.Nullable; 21 | import com.hannesdorfmann.adaptercommands.ItemChangedDetector; 22 | import java.util.List; 23 | 24 | /** 25 | * This class is responsible to calculate the difference between two lists and returns a list of 26 | * {@link AdapterCommand} that can be executed to enable RecyclerView animations. 27 | * 28 | * @author Hannes Dorfmann 29 | * @since 1.0.2 30 | */ 31 | public class ThreadSafeDiffCommandsCalculator extends DiffCommandsCalculator { 32 | 33 | public ThreadSafeDiffCommandsCalculator() { 34 | super(); 35 | } 36 | 37 | public ThreadSafeDiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff) { 38 | super(itemRangeInsertedOnFirstDiff); 39 | } 40 | 41 | public ThreadSafeDiffCommandsCalculator(ItemChangedDetector detector) { 42 | super(detector); 43 | } 44 | 45 | public ThreadSafeDiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff, 46 | @Nullable ItemChangedDetector detector) { 47 | super(itemRangeInsertedOnFirstDiff, detector); 48 | } 49 | 50 | /** 51 | * This method calculates the difference of previous list of items and the new list. 52 | * This call is thread safe 53 | * 54 | * @param newList The new items that we use to calculate the difference 55 | * @return List of commands 56 | */ 57 | @Override public synchronized List diff(@NonNull List newList) { 58 | return super.diff(newList); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /adaptercommands/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | adaptercommands 19 | 20 | -------------------------------------------------------------------------------- /adaptercommands/src/test/java/com/hannesdorfmann/adaptercommands/AdapterCommandProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import com.hannesdorfmann.adaptercommands.command.AdapterCommand; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import junit.framework.Assert; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.mockito.Mockito; 27 | 28 | /** 29 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 30 | */ 31 | public class AdapterCommandProcessorTest { 32 | 33 | private AdapterCommandProcessor processor; 34 | private RecyclerView.Adapter adapter; 35 | 36 | @Before public void init() { 37 | adapter = Mockito.mock(RecyclerView.Adapter.class); 38 | this.processor = new AdapterCommandProcessor(adapter); 39 | } 40 | 41 | @Test public void nullpointerException() { 42 | try { 43 | new AdapterCommandProcessor(null); 44 | Assert.fail("Nullpointer expected"); 45 | } catch (NullPointerException e) { 46 | Assert.assertEquals("adapter == null", e.getMessage()); 47 | } 48 | } 49 | 50 | @Test public void executeNullList() { 51 | try { 52 | processor.execute(null); 53 | Assert.fail("Nullpointer expected"); 54 | } catch (NullPointerException e) { 55 | Assert.assertEquals("commands == null", e.getMessage()); 56 | } 57 | } 58 | 59 | @Test public void executeCommands() { 60 | 61 | AdapterCommand c1 = Mockito.mock(AdapterCommand.class); 62 | AdapterCommand c2 = Mockito.mock(AdapterCommand.class); 63 | 64 | List commandList = new ArrayList<>(); 65 | commandList.add(c1); 66 | commandList.add(c2); 67 | 68 | processor.execute(commandList); 69 | 70 | Mockito.verify(c1, Mockito.times(1)).execute(adapter); 71 | Mockito.verify(c2, Mockito.times(1)).execute(adapter); 72 | } 73 | } -------------------------------------------------------------------------------- /adaptercommands/src/test/java/com/hannesdorfmann/adaptercommands/command/CommandsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import com.hannesdorfmann.adaptercommands.AdapterCommandProcessor; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mockito; 27 | import org.powermock.api.mockito.PowerMockito; 28 | import org.powermock.core.classloader.annotations.PrepareForTest; 29 | import org.powermock.modules.junit4.PowerMockRunner; 30 | 31 | /** 32 | * @author Hannes Dorfmann 33 | */ 34 | @RunWith(PowerMockRunner.class) @PrepareForTest(RecyclerView.Adapter.class) 35 | public class CommandsTest { 36 | 37 | private AdapterCommandProcessor processor; 38 | private RecyclerView.Adapter adapter; 39 | private List commands = new ArrayList<>(); 40 | 41 | @Before public void init() { 42 | adapter = PowerMockito.mock(RecyclerView.Adapter.class); 43 | processor = new AdapterCommandProcessor(adapter); 44 | commands.clear(); 45 | } 46 | 47 | @Test public void entireDatasetChangedCommand() { 48 | commands.add(new EntireDataSetChangedCommand()); 49 | processor.execute(commands); 50 | Mockito.verify(adapter, Mockito.only()).notifyDataSetChanged(); 51 | } 52 | 53 | @Test public void itemChanged() { 54 | commands.add(new ItemChangedCommand(1)); 55 | processor.execute(commands); 56 | Mockito.verify(adapter, Mockito.only()).notifyItemChanged(1); 57 | } 58 | 59 | @Test public void itemInserted() { 60 | commands.add(new ItemInsertedCommand(1)); 61 | processor.execute(commands); 62 | Mockito.verify(adapter, Mockito.only()).notifyItemInserted(1); 63 | } 64 | 65 | @Test public void itemMoved() { 66 | commands.add(new ItemMovedCommand(1, 2)); 67 | processor.execute(commands); 68 | Mockito.verify(adapter, Mockito.only()).notifyItemMoved(Mockito.eq(1), Mockito.eq(2)); 69 | } 70 | 71 | 72 | @Test public void itemRemoved() { 73 | commands.add(new ItemRemovedCommand(1)); 74 | processor.execute(commands); 75 | Mockito.verify(adapter, Mockito.only()).notifyItemRemoved(1); 76 | } 77 | 78 | @Test public void itemRangeChanged() { 79 | commands.add(new ItemRangeChangedCommand(1, 2)); 80 | processor.execute(commands); 81 | Mockito.verify(adapter, Mockito.only()).notifyItemRangeChanged(Mockito.eq(1), Mockito.eq(2)); 82 | } 83 | 84 | @Test public void itemRangeInserted() { 85 | commands.add(new ItemRangeInsertedCommand(1, 2)); 86 | processor.execute(commands); 87 | Mockito.verify(adapter, Mockito.only()).notifyItemRangeInserted(Mockito.eq(1), Mockito.eq(2)); 88 | } 89 | 90 | @Test public void itemRangeRemoved() { 91 | commands.add(new ItemRangeRemovedCommand(1, 2)); 92 | processor.execute(commands); 93 | Mockito.verify(adapter, Mockito.only()).notifyItemRangeRemoved(Mockito.eq(1), Mockito.eq(2)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /adaptercommands/src/test/java/com/hannesdorfmann/adaptercommands/command/DiffCommandsCalculatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.command; 18 | 19 | import com.hannesdorfmann.adaptercommands.ItemChangedDetector; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | 27 | /** 28 | * @author Hannes Dorfmann 29 | */ 30 | public class DiffCommandsCalculatorTest { 31 | 32 | private DiffCommandsCalculator calculator; 33 | 34 | @Before public void init() { 35 | calculator = new DiffCommandsCalculator(new Detector()); 36 | } 37 | 38 | @Test public void firstTime() { 39 | 40 | List newItems = newList("a", "b"); 41 | List commands = calculator.diff(newItems); 42 | 43 | Assert.assertEquals(1, commands.size()); 44 | Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); 45 | } 46 | 47 | @Test public void firstTimeItemRangeInserted() { 48 | DiffCommandsCalculator calculator = new DiffCommandsCalculator<>(true); 49 | List newItems = newList("a", "b"); 50 | List commands = calculator.diff(newItems); 51 | 52 | Assert.assertEquals(1, commands.size()); 53 | assertContainCommand(commands, new ItemRangeInsertedCommand(0, 2)); 54 | } 55 | 56 | @Test public void firstTimeEmptyList() { 57 | List newItems = new ArrayList<>(); 58 | List commands = calculator.diff(newItems); 59 | 60 | Assert.assertEquals(1, commands.size()); 61 | Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); 62 | } 63 | 64 | @Test public void nullList() { 65 | try { 66 | calculator.diff(null); 67 | Assert.fail("NullPointerException expected"); 68 | } catch (NullPointerException e) { 69 | Assert.assertEquals("newList == null", e.getMessage()); 70 | } 71 | } 72 | 73 | @Test public void insertAndChange() { 74 | 75 | // Warmup 76 | List items = newList("a", "b", "c"); 77 | calculator.diff(items); 78 | 79 | // 80 | // Test insert 81 | // 82 | items.add(1, new Item("a2")); 83 | items.add(3, new Item("b2")); 84 | items.add(4, new Item("b3")); 85 | items.add(5, new Item("b4")); 86 | items.add(new Item("c2")); 87 | 88 | List commands = calculator.diff(items); 89 | Assert.assertEquals(5, commands.size()); 90 | 91 | assertContainCommand(commands, new ItemInsertedCommand(1)); 92 | assertContainCommand(commands, new ItemInsertedCommand(3)); 93 | assertContainCommand(commands, new ItemInsertedCommand(4)); 94 | assertContainCommand(commands, new ItemInsertedCommand(5)); 95 | assertContainCommand(commands, new ItemInsertedCommand(7)); 96 | 97 | // 98 | // Test changes 99 | // 100 | 101 | // Changes with detector 102 | items.set(0, new Item("a", "newOtherValueA")); 103 | items.set(2, new Item("b", "newOtherValueB")); 104 | items.set(3, new Item("b2", "newOtherValueB2")); 105 | items.set(4, new Item("b3", "newOtherValueB3")); 106 | items.set(7, new Item("c2", "newOtherValuec2")); 107 | 108 | commands = calculator.diff(items); 109 | 110 | Assert.assertEquals(5, commands.size()); 111 | assertContainCommand(commands, new ItemChangedCommand(0)); 112 | assertContainCommand(commands, new ItemChangedCommand(2)); 113 | assertContainCommand(commands, new ItemChangedCommand(3)); 114 | assertContainCommand(commands, new ItemChangedCommand(4)); 115 | assertContainCommand(commands, new ItemChangedCommand(7)); 116 | } 117 | 118 | @Test public void changesWithoutDetector() { 119 | 120 | // Warmup 121 | List items = newList("a", "b", "c"); 122 | items.add(1, new Item("a2")); 123 | items.add(3, new Item("b2")); 124 | items.add(4, new Item("b3")); 125 | items.add(5, new Item("b4")); 126 | items.add(new Item("c2")); 127 | 128 | calculator = new DiffCommandsCalculator<>(); 129 | calculator.diff(items); 130 | 131 | // Changes without detector 132 | items.set(0, new Item("a", "newValueA")); 133 | items.set(2, new Item("b", "newValueB")); 134 | items.set(3, new Item("b2", "newValueB2")); 135 | items.set(4, new Item("b3", "newValueB3")); 136 | items.set(7, new Item("c2", "newValuec2")); 137 | 138 | List commands = calculator.diff(items); 139 | Assert.assertTrue(commands.isEmpty()); 140 | } 141 | 142 | @Test public void move() { 143 | 144 | // Warmup 145 | ItemChangedDetector changeDetector = new Detector(); 146 | List items = newList("a", "b", "c", "d", "e"); 147 | calculator.diff(items); 148 | 149 | // 150 | // Move elements 151 | // 152 | Item a = items.remove(0); 153 | items.add(3, a); 154 | Item c = items.remove(1); 155 | items.add(4, c); 156 | 157 | List commands = calculator.diff(items); 158 | 159 | // TODO update once move commands are implemented 160 | Assert.assertEquals(4, commands.size()); 161 | assertContainCommand(commands, new ItemRemovedCommand(0)); 162 | assertContainCommand(commands, new ItemInsertedCommand(2)); 163 | assertContainCommand(commands, new ItemRemovedCommand(1)); 164 | assertContainCommand(commands, new ItemInsertedCommand(4)); 165 | } 166 | 167 | @Test public void remove() { 168 | // Warmup 169 | ItemChangedDetector changeDetector = new Detector(); 170 | List items = newList("a", "b", "c", "d", "e", "f"); 171 | calculator.diff(items); 172 | 173 | items.remove(1); 174 | items.remove(3); 175 | 176 | List commands = calculator.diff(items); 177 | Assert.assertEquals(2, commands.size()); 178 | assertContainCommand(commands, new ItemRemovedCommand(1)); 179 | assertContainCommand(commands, new ItemRemovedCommand(3)); 180 | } 181 | 182 | @Test public void comparingEmptyList() { 183 | List commands = calculator.diff(Collections.emptyList()); 184 | Assert.assertEquals(1, commands.size()); 185 | Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); 186 | 187 | // Apply changes on empty list 188 | List commands2 = calculator.diff(Collections.emptyList()); 189 | Assert.assertTrue(commands2.isEmpty()); 190 | } 191 | 192 | private List newList(String... items) { 193 | ArrayList list = new ArrayList<>(); 194 | for (String item : items) { 195 | list.add(new Item(item)); 196 | } 197 | return list; 198 | } 199 | 200 | private T assertContainCommand(List commands, 201 | T equalsCommand) { 202 | 203 | for (AdapterCommand c : commands) { 204 | if (c.equals(equalsCommand)) { 205 | return (T) c; 206 | } 207 | } 208 | 209 | Assert.fail("Expected command " + equalsCommand + " but not found"); 210 | return null; 211 | } 212 | 213 | static class Item { 214 | public String id; 215 | public String value; 216 | 217 | public Item(String id) { 218 | this(id, id); 219 | } 220 | 221 | public Item(String id, String value) { 222 | this.id = id; 223 | this.value = value; 224 | } 225 | 226 | @Override public boolean equals(Object o) { 227 | if (this == o) return true; 228 | if (o == null || getClass() != o.getClass()) return false; 229 | 230 | Item item = (Item) o; 231 | 232 | return !(id != null ? !id.equals(item.id) : item.id != null); 233 | } 234 | 235 | @Override public int hashCode() { 236 | return id != null ? id.hashCode() : 0; 237 | } 238 | 239 | @Override public String toString() { 240 | return id; 241 | } 242 | } 243 | 244 | static class Detector implements ItemChangedDetector { 245 | @Override public boolean hasChanged(Item oldItem, Item newItem) { 246 | return !oldItem.value.equals(newItem.value); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | bin/ 7 | gen/ 8 | classes/ 9 | gen-external-apklibs/ 10 | 11 | # Eclipse project files 12 | .classpath 13 | .project 14 | .metadata 15 | .settings 16 | 17 | # IntelliJ files 18 | .idea 19 | *.iml 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Windows files 25 | Thumbs.db 26 | 27 | # vi swap files 28 | *.swp 29 | 30 | # backup files 31 | *.bak 32 | 33 | 34 | # Files for the Dalvik VM 35 | *.dex 36 | 37 | # Java class files 38 | *.class 39 | 40 | # Generated files 41 | bin/ 42 | gen/ 43 | 44 | # Gradle files 45 | .gradle/ 46 | build/ 47 | .gradle 48 | 49 | #maven files 50 | target/ 51 | /null 52 | 53 | # Local configuration file (sdk path, etc) 54 | 55 | local.properties 56 | 57 | # Proguard folder generated by Eclipse 58 | proguard/ 59 | 60 | #Log Files 61 | *.log -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | apply plugin: 'com.android.application' 18 | 19 | android { 20 | compileSdkVersion 23 21 | buildToolsVersion "23.0.3" 22 | 23 | defaultConfig { 24 | applicationId "com.hannesdorfmann.adaptercommands.example" 25 | minSdkVersion 14 26 | targetSdkVersion 23 27 | versionCode 1 28 | versionName "1.0" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | compile fileTree(dir: 'libs', include: ['*.jar']) 40 | testCompile 'junit:junit:4.12' 41 | compile 'com.android.support:appcompat-v7:' + rootProject.ext.appcompat7Version 42 | compile 'com.android.support:recyclerview-v7:' + rootProject.ext.recyclerviewVersion 43 | 44 | compile 'com.jakewharton:butterknife:7.0.1' 45 | compile project(':adaptercommands') 46 | compile 'io.reactivex:rxandroid:1.1.0' 47 | } 48 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/hannes/android-sdks/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/hannesdorfmann/adaptercommands/example/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.adaptercommands.example; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/hannesdorfmann/adaptercommands/example/Item.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.example; 18 | 19 | /** 20 | * @author Hannes Dorfmann 21 | */ 22 | public class Item { 23 | 24 | int id; 25 | int color; 26 | 27 | public Item(int id, int color) { 28 | this.id = id; 29 | this.color = color; 30 | } 31 | 32 | @Override public boolean equals(Object o) { 33 | if (this == o) return true; 34 | if (o == null || getClass() != o.getClass()) return false; 35 | 36 | Item item = (Item) o; 37 | 38 | return id == item.id; 39 | } 40 | 41 | @Override public int hashCode() { 42 | return id; 43 | } 44 | 45 | @Override public String toString() { 46 | return Integer.toString(id); 47 | } 48 | 49 | public Item copy() { 50 | return new Item(id, color); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/hannesdorfmann/adaptercommands/example/ItemAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.example; 18 | 19 | import android.content.Context; 20 | import android.graphics.Color; 21 | import android.support.v7.widget.RecyclerView; 22 | import android.view.LayoutInflater; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.widget.TextView; 26 | import java.util.List; 27 | 28 | /** 29 | * @author Hannes Dorfmann 30 | */ 31 | public class ItemAdapter extends RecyclerView.Adapter { 32 | 33 | static class ItemViewHolder extends RecyclerView.ViewHolder { 34 | 35 | public ItemViewHolder(View itemView) { 36 | super(itemView); 37 | } 38 | } 39 | 40 | LayoutInflater inflater; 41 | List items; 42 | 43 | int rgb[] = new int[3]; 44 | double rgbRes[] = new double[3]; 45 | 46 | public ItemAdapter(Context context, List items) { 47 | this.inflater = LayoutInflater.from(context); 48 | this.items = items; 49 | } 50 | 51 | @Override public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | return new ItemViewHolder(inflater.inflate(R.layout.item, parent, false)); 53 | } 54 | 55 | @Override public void onBindViewHolder(ItemViewHolder holder, int position) { 56 | Item item = items.get(position); 57 | int color = item.color; 58 | TextView tv = (TextView) holder.itemView; 59 | tv.setBackgroundColor(color); 60 | tv.setText(Integer.toString(item.id)); 61 | tv.setTextColor(calculateTextColor(color)); 62 | } 63 | 64 | private int calculateTextColor(int color) { 65 | 66 | rgb[0] = Color.red(color); 67 | rgb[1] = Color.green(color); 68 | rgb[2] = Color.blue(color); 69 | 70 | for (int i = 0; i < rgb.length; i++) { 71 | double c = rgb[i]; 72 | c = c / 255.0; 73 | if (c <= 0.03928) { 74 | c = c / 12.92; 75 | } else { 76 | c = Math.pow((c + 0.055) / 1.055, 2.4); 77 | } 78 | rgbRes[i] = c; 79 | } 80 | 81 | double luminance = 0.2126 * rgbRes[0] + 0.7152 * rgbRes[1] + 0.0722 * rgbRes[2]; 82 | 83 | if (luminance > 0.179) { 84 | return Color.BLACK; 85 | } else { 86 | return Color.WHITE; 87 | } 88 | 89 | } 90 | 91 | @Override public int getItemCount() { 92 | return items == null ? 0 : items.size(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/hannesdorfmann/adaptercommands/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Hannes Dorfmann 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 | package com.hannesdorfmann.adaptercommands.example; 18 | 19 | import android.graphics.Color; 20 | import android.os.Bundle; 21 | import android.support.annotation.ColorInt; 22 | import android.support.v4.widget.SwipeRefreshLayout; 23 | import android.support.v7.app.AppCompatActivity; 24 | import android.support.v7.widget.GridLayoutManager; 25 | import android.support.v7.widget.RecyclerView; 26 | import android.util.Log; 27 | import android.widget.Toast; 28 | import butterknife.Bind; 29 | import butterknife.BindColor; 30 | import butterknife.BindInt; 31 | import butterknife.ButterKnife; 32 | import butterknife.OnClick; 33 | import com.hannesdorfmann.adaptercommands.AdapterCommandProcessor; 34 | import com.hannesdorfmann.adaptercommands.command.DiffCommandsCalculator; 35 | import com.hannesdorfmann.adaptercommands.ItemChangedDetector; 36 | import com.hannesdorfmann.adaptercommands.command.AdapterCommand; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.Random; 40 | 41 | public class MainActivity extends AppCompatActivity 42 | implements SwipeRefreshLayout.OnRefreshListener { 43 | 44 | @Bind(R.id.refreshLayout) SwipeRefreshLayout refreshLayout; 45 | @Bind(R.id.recyclerView) RecyclerView recyclerView; 46 | @BindInt(R.integer.columns) int columns; 47 | @BindColor(R.color.amber) int amber; 48 | @BindColor(R.color.green) int green; 49 | @BindColor(R.color.purple) int purple; 50 | 51 | Runnable refreshRunnable = new Runnable() { 52 | @Override public void run() { 53 | for (int i = 0; i < columns - 1; i++) { 54 | items.add(0, new Item(id(), randomColor())); 55 | } 56 | refreshLayout.setRefreshing(false); 57 | updateAdapter(); 58 | } 59 | }; 60 | 61 | ItemChangedDetector changeDetector = new ItemChangedDetector() { 62 | @Override public boolean hasChanged(Item oldItem, Item newItem) { 63 | return oldItem.color != newItem.color; 64 | } 65 | }; 66 | 67 | List items = new ArrayList(); 68 | Random random = new Random(); 69 | int lastId = 0; 70 | ItemAdapter adapter; 71 | AdapterCommandProcessor commandProcessor; 72 | DiffCommandsCalculator commandsCalculator = new DiffCommandsCalculator(changeDetector); 73 | 74 | @Override protected void onCreate(Bundle savedInstanceState) { 75 | super.onCreate(savedInstanceState); 76 | setContentView(R.layout.activity_main); 77 | ButterKnife.bind(this); 78 | 79 | refreshLayout.setOnRefreshListener(this); 80 | 81 | adapter = new ItemAdapter(this, items); 82 | recyclerView.setAdapter(adapter); 83 | recyclerView.setLayoutManager(new GridLayoutManager(this, columns)); 84 | 85 | commandProcessor = new AdapterCommandProcessor(adapter); 86 | 87 | // initial items 88 | items.add(new Item(id(), amber)); 89 | items.add(new Item(id(), green)); 90 | items.add(new Item(id(), purple)); 91 | updateAdapter(); 92 | } 93 | 94 | @OnClick(R.id.add) public void addClicked() { 95 | 96 | int addCount = random.nextInt(3) + 1; 97 | 98 | for (int i = 0; i < addCount; i++) { 99 | int position = items.size() == 0 ? 0 : random.nextInt(items.size()); 100 | Item item = new Item(id(), randomColor()); 101 | items.add(position, item); 102 | //adapter.notifyItemInserted(position); 103 | Log.d("Items", "added Item(" + item + ") at position " + position); 104 | } 105 | 106 | updateAdapter(); 107 | } 108 | 109 | @OnClick(R.id.remove) public void removeClicked() { 110 | 111 | if (items.size() < 3) { 112 | items.clear(); 113 | updateAdapter(); 114 | return; 115 | } 116 | 117 | int removeCount = 3; 118 | 119 | for (int i = 0; i < removeCount; i++) { 120 | int position = random.nextInt(items.size()); 121 | Item item = items.remove(position); 122 | //adapter.notifyItemInserted(position); 123 | Log.d("Items", "removed Item(" + item + ") at position " + position); 124 | } 125 | 126 | updateAdapter(); 127 | } 128 | 129 | @OnClick(R.id.move) public void moveClicked() { 130 | if (items.size() < 2) { 131 | Toast.makeText(this, "Minimum 2 items required", Toast.LENGTH_SHORT).show(); 132 | return; 133 | } 134 | 135 | int middle = items.size() / 2; 136 | Item i = items.remove(0); 137 | items.add(middle, i); 138 | Log.d("Items", "Moved Item(" + i + ") from position 0 to " + (items.size() - 1)); 139 | i = items.remove(1); 140 | items.add(i); 141 | Log.d("Items", "Moved Item(" + i + ") from position 0 to " + middle); 142 | 143 | updateAdapter(); 144 | } 145 | 146 | @OnClick(R.id.change) public void changeClicked() { 147 | if (items.size() < 3) { 148 | Toast.makeText(this, "Minimum 3 items required", Toast.LENGTH_SHORT).show(); 149 | } 150 | int changeCount = random.nextInt(3) + 1; 151 | for (int i = 0; i < changeCount; i++) { 152 | int position = random.nextInt(items.size()); 153 | Item item = items.get(position).copy(); 154 | item.color = randomColor(); 155 | items.set(position, item); 156 | Log.d("Items", "changed Item(" + item + ") at position" + position); 157 | } 158 | 159 | updateAdapter(); 160 | } 161 | 162 | @OnClick(R.id.notifyChanged) public void notifyChangedClicked() { 163 | adapter.notifyDataSetChanged(); 164 | } 165 | 166 | @OnClick(R.id.complex) public void complexClicked() { 167 | 168 | if (items.size() < 3) { 169 | Toast.makeText(this, "Minimum 3 items required", Toast.LENGTH_SHORT).show(); 170 | return; 171 | } 172 | 173 | int removals = random.nextInt(3) + 1; 174 | for (int i = 0; i < removals; i++) { 175 | if (items.isEmpty()) { 176 | continue; 177 | } 178 | int removePosition = random.nextInt(items.size()); 179 | Item item = items.remove(removePosition); 180 | Log.d("Items", "removed Item(" + item + ") at position " + removePosition); 181 | } 182 | 183 | int changeCount = random.nextInt(3) + 1; 184 | for (int i = 0; i < changeCount; i++) { 185 | int position = random.nextInt(items.size()); 186 | Item item = items.get(position).copy(); 187 | item.color = randomColor(); 188 | items.set(position, item); 189 | Log.d("Items", "changed Item(" + item + ") at position " + position); 190 | } 191 | 192 | int inserts = random.nextInt(4) + 1; 193 | for (int i = 0; i < inserts; i++) { 194 | int addPosition = items.isEmpty() ? 0 : random.nextInt(items.size()); 195 | Item item = new Item(id(), randomColor()); 196 | items.add(addPosition, item); 197 | Log.d("Items", "Added Item(" + item + ") at position " + addPosition); 198 | } 199 | 200 | updateAdapter(); 201 | } 202 | 203 | private void updateAdapter() { 204 | List commands = commandsCalculator.diff(items); 205 | Log.d("Items", "commands " + commands); 206 | commandProcessor.execute(commands); 207 | } 208 | 209 | private int id() { 210 | return lastId++; 211 | } 212 | 213 | @ColorInt private int randomColor() { 214 | return Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256)); 215 | } 216 | 217 | @Override public void onRefresh() { 218 | refreshLayout.postDelayed(refreshRunnable, 1500); 219 | } 220 | 221 | @Override protected void onDestroy() { 222 | super.onDestroy(); 223 | refreshLayout.removeCallbacks(refreshRunnable); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 32 | 33 | 38 | > 39 | 40 | 41 |