diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index c4df0551..2e94a6e0 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,10 +1,11 @@
Please check off the following points BEFORE submitting a new issue / asking support.
- [ ] Write a self explanatory Subject.
+- [ ] Check if the issue has already been raised by someone else (see closed issue).
1) For Bugs / Support
-- [ ] What you've tried (If necessary, indicate library Version / Android version / hardware)
-- [ ] Expected behavior and actual behavior (problem encountered)
-- [ ] Steps to reproduce the problem (also include code snippets if you think will help)
+- [ ] What you've tried (If necessary, indicate library Version / Android version / hardware).
+- [ ] Expected behavior and actual behavior (problem encountered).
+- [ ] Steps to reproduce the problem (also include code snippets if you think will help).
2) For questions / new feature / improvement
- [ ] Simply Erase all this text and ask your question;
diff --git a/README.md b/README.md
index 902d30dc..00cb0331 100644
--- a/README.md
+++ b/README.md
@@ -2,40 +2,43 @@
[![Download](https://api.bintray.com/packages/davideas/maven/flexible-adapter/images/download.svg) ](https://bintray.com/davideas/maven/flexible-adapter/_latestVersion)
[![API](https://img.shields.io/badge/API-14%2B-green.svg?style=flat)](https://android-arsenal.com/api?level=14)
[![Licence](https://img.shields.io/badge/Licence-Apache2-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+[![Methods and Size](https://img.shields.io/badge/Methods%20and%20Size-core:%201162%20|%20deps:%2021349%20|%20159%20KB-e91e63.svg)](http://www.methodscount.com/?lib=eu.davidea%3Aflexible-adapter%3A5.0.0-rc1)
# FlexibleAdapter
-###### Fast and Versatile Adapter for your RecyclerView
-- NEW! Beta version: [v5.0.0-b8](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b8) built on 2016.09.17 (144KB)
-
####ANNOUNCEMENT: Important and Revolutionary changes are foreseen in v5.0.0. Please see [issues](https://github.com/davideas/FlexibleAdapter/issues) and [releases](https://github.com/davideas/FlexibleAdapter/releases).
+###### Fast and versatile Adapter for your RecyclerView
+- **NEW!** First release candidate: [v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) built on 2017.01.14
+- If you come from previous versions, update your code following the Wiki page [Migrations](https://github.com/davideas/FlexibleAdapter/wiki/Migrations).
+
> When initially Android team introduced the RecyclerView widget, we had to implement a custom Adapter in several applications, again and again to provide the items for our views.
We didn't know how to add selection and to combine all the use cases in the same Adapter.
Since I created this library, it has become easy to configure how views will be displayed in a list, and now, nobody wants to use a ListView anymore.
-The idea behind is to regroup many functionalities in a unique library, without the need to customize and import several third libraries not compatible among them.
+The idea behind is to regroup multiple features in a unique library, without the need to customize and import several third libraries not compatible among them.
The FlexibleAdapter helps developers to simplify this process without worrying too much about the Adapter anymore. It's easy to extend, it has predefined logic for different situations and prevents common mistakes.
-This library is configurable and it guides the developers to create a better user experience and now, even more with the new ViewHolders and new actions.
+This library is configurable and it guides the developers to create a better user experience and now, even more with the new features.
-#### Main functionalities
+#### Main features
* Simple item selection with ripple effect, Single & Multi selection mode.
* Restore deleted items, **NEW** works with Expandable items too!
* Customizable [FastScroller](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-FastScroller), **NEW** now in the library supporting all the 3 Layouts.
* Customizable divider item decoration.
* Add and Remove items with custom animations.
-* [SearchFilter](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Search-Filter) with Spannable text, **NEW** result is animated. Works with sub items too!
-* **NEW!** [High performance](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Search-Filter#performance-result-when-animations-are-active) on big list filtered with synchronization animations < 1'', slow phones < 2,5'' (available from beta8).
-* **NEW!** Auto mapping ViewTypes with [Item interfaces](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Item-Interfaces).
-* **NEW!** Predefined [ViewHolders](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-ViewHolders).
-* **NEW!** [Headers/Sections](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Headers-and-Sections) with sticky behaviour fully clickable, collapsible, automatic linkage!
-* **NEW!** Expandable items with Selection Coherence, multi-level expansion.
-* **NEW!** [Drag&Drop and Swipe-To-Dismiss](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Drag&Drop-and-Swipe#swiping-the-front-view) with Leave-Behind pattern, with Selection Coherence.
-* **NEW!** Customizable Scrolling-Animations based on adapter position and beyond.
-* **NEW!** Innovative [EndlessScroll](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-On-Load-More) (No OnScrollListener).
+* Async [SearchFilter](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Search-Filter) with Spannable text, **NEW** result list is animated. Works with sub items too!
+* **NEW!** [High performance](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Search-Filter#performance-result-when-animations-are-active) updates and filter on big list.
+* **NEW!** Auto mapping multi view types with [Item interfaces](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Item-Interfaces).
+* **NEW!** Predefined [ViewHolders](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-ViewHolders) with callbacks.
+* **NEW!** [Headers and Sections](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Headers-and-Sections) with sticky behaviour fully clickable and collapsible, with elevation, transparency and automatic linkage!
+* **NEW!** [Scrollable Headers and Footers](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Scrollable-Headers-and-Footers) items that lay respectively at the top and at the bottom of the main items.
+* **NEW!** Expandable items with _Selection Coherence_ and multi-level expansion.
+* **NEW!** [Drag&Drop and Swipe-To-Dismiss](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Drag&Drop-and-Swipe#swiping-the-front-view) with Leave-Behind pattern and with _Selection Coherence_.
+* **NEW!** Customizable scrolling animations based on adapter position and beyond.
+* **NEW!** Innovative [EndlessScroll](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-On-Load-More) (_No OnScrollListener_).
* **NEW!** UndoHelper & [ActionModeHelper](https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-ActionModeHelper).
-* **NEW!** DrawableUtils for dynamic backgrounds with ripple (No XML).
+* **NEW!** DrawableUtils for dynamic backgrounds with ripple (_No XML_).
* **NEW!** Easy runtime position calculation for adding/moving items in sections.
* **NEW!** [Wiki](https://github.com/davideas/FlexibleAdapter/wiki/) pages documentation.
@@ -43,42 +46,43 @@ This library is configurable and it guides the developers to create a better use
```
repositories {
jcenter()
- maven {url = "http://dl.bintray.com/davideas/maven" }
maven {url = "https://oss.sonatype.org/content/repositories/snapshots/" } //For Snapshots
}
```
```
dependencies {
- //Using JCenter
- compile 'eu.davidea:flexible-adapter:5.0.0-b8'
+ // Using JCenter
+ compile 'eu.davidea:flexible-adapter:5.0.0-rc1'
- //Using MavenSnapshots repository for continuous updates from my development
+ // Using MavenSnapshots repository for continuous updates from my development
compile 'eu.davidea:flexible-adapter:5.0.0-SNAPSHOT'
}
```
# Wiki!
I strongly recommend to read the **new [Wiki](https://github.com/davideas/FlexibleAdapter/wiki) pages**, where you can find a comprehensive Tutorial*.
-Wiki pages have been completely reviewed to support all the coming functionalities from 5.0.0.
+Wiki pages have been completely reviewed to support all the coming features of version 5.0.0.
\* = _Pages are under heavy revision, working in progress_ :-)
#### Pull requests / Issues / Improvement requests
Feel free to contribute and ask!
Active discussions:
+- [The next steps of your development](https://github.com/davideas/FlexibleAdapter/issues/224).
- [Snapshots and Pre-Releases for FlexibleAdapter v5.0.0](https://github.com/davideas/FlexibleAdapter/issues/39).
- [Documentation](https://github.com/davideas/FlexibleAdapter/issues/120).
#### Under the hood
-Some simple functionalities have been implemented thanks to some Blogs (see at the bottom of the page), merged and methods have been improved for speed and scalability, for all Activities that use a RecyclerView.
+Some simple features have been implemented, thanks to some Blogs (see at the bottom of the page), merged and methods have been improved for speed and scalability.
-* At lower level there is `SelectableAdapter` class. It provides selection functionalities and it's able to _maintain the state_ after the rotation: you just need to call the onSave/onRestore methods from the Activity!
+* At lower level there is `SelectableAdapter` class. It provides selection features and it's able to _maintain the state_ after the rotation: you just need to call the onSave/onRestore methods from the Activity!
* At middle level, the `AnimatorAdapter` class has been added to give some animation at startup and when user scrolls.
* At front level, the core class `FlexibleAdapter`. It holds and handles the main list, performs actions on all different types of item paying attention at the adding and removal of the items, as well as the new concept of "selection coherence".
+* New useful extensions and helpers have been added during the time to simplify the development.
* Item interfaces and predefined ViewHolders complete the whole library giving more actions to the items and configuration options to the developers and the end user.
# Showcase of the demo App
-You can download the latest demo App from the latest release page.
+You can download the latest demo App from the latest release page OR run it with the emulator.
![Drag Grid & Overall](/screenshots/drag_grid_overall.png)
![Secondary Functionalities](/screenshots/secondary_functionalities.png)
@@ -98,25 +102,26 @@ You can download the latest demo App from the latest release page.
# Change Log
###### Latest release
-[v5.0.0-b8](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b8) - 2016.09.17
+[v5.0.0-rc1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-rc1) - 2017.01.14
###### Old releases
+[v5.0.0-b8](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b8) - 2016.09.17 |
[v5.0.0-b7](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b7) - 2016.06.20 |
-[v5.0.0-b6](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b6) - 2016.05.01 |
-[v5.0.0-b5](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b5) - 2016.04.04 |
-[v5.0.0-b4](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b4) - 2016.02.21
-[v5.0.0-b3](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b3) - 2016.02.08 |
-[v5.0.0-b2](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b2) - 2016.01.31 |
+[v5.0.0-b6](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b6) - 2016.05.01 |
+[v5.0.0-b5](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b5) - 2016.04.04
+[v5.0.0-b4](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b4) - 2016.02.21 |
+[v5.0.0-b3](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b3) - 2016.02.08 |
+[v5.0.0-b2](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b2) - 2016.01.31 |
[v5.0.0-b1](https://github.com/davideas/FlexibleAdapter/releases/tag/5.0.0-b1) - 2016.01.03
-[v4.2.0](https://github.com/davideas/FlexibleAdapter/releases/tag/4.2.0) - 2015.12.12 |
-[v4.1.0](https://github.com/davideas/FlexibleAdapter/releases/tag/4.1.0) - 2015.11.29 |
-[v4.0.1](https://github.com/davideas/FlexibleAdapter/releases/tag/4.0.1) - 2015.11.01 |
+[v4.2.0](https://github.com/davideas/FlexibleAdapter/releases/tag/4.2.0) - 2015.12.12 |
+[v4.1.0](https://github.com/davideas/FlexibleAdapter/releases/tag/4.1.0) - 2015.11.29 |
+[v4.0.1](https://github.com/davideas/FlexibleAdapter/releases/tag/4.0.1) - 2015.11.01 |
[v4.0.0](https://github.com/davideas/FlexibleAdapter/releases/tag/4.0.0) - 2015.10.18
-[v3.1](https://github.com/davideas/FlexibleAdapter/releases/tag/v3.1) - 2015.08.18 |
-[v3.0](https://github.com/davideas/FlexibleAdapter/releases/tag/v3.0) - 2015.07.29 |
-[v2.2](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.2) - 2015.07.20 |
-[v2.1](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.1) - 2015.07.03 |
-[v2.0](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.0) - 2015.06.19 |
+[v3.1](https://github.com/davideas/FlexibleAdapter/releases/tag/v3.1) - 2015.08.18 |
+[v3.0](https://github.com/davideas/FlexibleAdapter/releases/tag/v3.0) - 2015.07.29 |
+[v2.2](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.2) - 2015.07.20 |
+[v2.1](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.1) - 2015.07.03 |
+[v2.0](https://github.com/davideas/FlexibleAdapter/releases/tag/v2.0) - 2015.06.19 |
[v1.0](https://github.com/davideas/FlexibleAdapter/releases/tag/v1.0) - 2015.05.03
# Limitations
@@ -131,18 +136,20 @@ I've used these blogs as starting point:
Special thanks goes to Martin Guillon ([Akylas](https://github.com/Akylas)) to have contributed at the development of the new technique for the Sticky Header.
# Imported libraries
-- For the moment only [LollipopContactsRecyclerViewFastScroller](https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller) has been imported, improved and adapted to work in conjunction with `AnimatorAdapter`.
-- The library [sticky-headers-recyclerview](https://github.com/timehop/sticky-headers-recyclerview) was initially imported and super-optimized for _FlexibleAdapter_, then it was removed in favor of the new technique able to keep the _View_ and so to handle the click events.
+- The library [LollipopContactsRecyclerViewFastScroller](https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller) has been imported, improved and adapted to work in conjunction with `AnimatorAdapter`.
+- The library [sticky-headers-recyclerview](https://github.com/timehop/sticky-headers-recyclerview) was initially imported, then it was removed in favor of the new technique able to manage a real _View_ and so to handle the click events.
# Apps that use this Adapter
-It will be a pleasure to add your App here.
-- [Socio - Shake and Connect!](https://play.google.com/store/apps/details?id=com.atsocio.socio)
-- [BNVR Client](https://play.google.com/store/apps/details?id=ru.beward.bnvr)
-- [Module.org](https://play.google.com/store/apps/details?id=org.module.app)
+It will be a pleasure to add your App here, once it is published.
+
+[Module.org](https://play.google.com/store/apps/details?id=org.module.app) |
+[Socio - Shake and Connect!](https://play.google.com/store/apps/details?id=com.atsocio.socio) |
+[Shibagram](https://play.google.com/store/apps/details?id=com.apripachkin.shibagram) |
+[BNVR Client](https://play.google.com/store/apps/details?id=ru.beward.bnvr)
# License
- Copyright 2015-2016 Davide Steduto
+ Copyright 2015-2017 Davide Steduto
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/build.gradle b/build.gradle
index 54337a48..3c4237bd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,10 +6,10 @@ ext {
developerEmail = "dave.dna@gmail.com"
//Library
- libraryCode = 18
- libraryVersion = "5.0.0-b8"
+ libraryCode = 19
+ libraryVersion = "5.0.0-rc1"
libraryDate = " built on " + getDate()
- libraryDescription = "1 Adapter for SelectionMode, Undo, ViewHolders, Filter, FastScroller, Animations, Sticky Headers, Expandable, Draggable, Swipeable, EndlessScroll :-)"
+ libraryDescription = "1 Adapter for SelectionMode, ViewHolders, AsyncFilter, FastScroller, Animations, Undo, Sections, Sticky Headers, Scrollable Headers and Footers, EndlessScroll, Expandable, Draggable, Swipeable :-)"
libraryName = "FlexibleAdapter"
packageExt = "aar"
@@ -23,9 +23,9 @@ ext {
//Support and Build tools version
minSdk = 14
- targetSdk = 24
- buildTools = "23.0.3"
- supportLib = "24.2.1"
+ targetSdk = 25
+ buildTools = "25.0.2"
+ supportLib = "25.1.0"
//Support Libraries dependencies
supportDependencies = [
@@ -49,7 +49,7 @@ def getDate() {
return date.format('yyyy.MM.dd')
}
-//Avoid Javadoc Lint failures when using Java8
+//Avoid JavaDoc Lint failures when using Java8
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
tasks.withType(Javadoc) {
@@ -65,7 +65,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.0'
+ classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath "com.github.dcendents:android-maven-gradle-plugin:1.5"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7"
diff --git a/flexible-adapter-app/build.gradle b/flexible-adapter-app/build.gradle
index d1f833d8..eb0539d4 100644
--- a/flexible-adapter-app/build.gradle
+++ b/flexible-adapter-app/build.gradle
@@ -41,9 +41,9 @@ android {
dependencies {
//Testing
androidTestCompile supportDependencies.annotations
- androidTestCompile 'com.android.support.test:runner:0.4.1'
- androidTestCompile 'com.android.support.test:rules:0.4.1'
- testCompile 'org.robolectric:robolectric:3.1.1'
+ androidTestCompile 'com.android.support.test:runner:0.5'
+ androidTestCompile 'com.android.support.test:rules:0.5'
+ testCompile 'org.robolectric:robolectric:3.1.2'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.0.71-beta'
@@ -51,7 +51,7 @@ dependencies {
compile project (":flexible-adapter")
//FlipView
- compile "eu.davidea:flipview:1.1.1"
+ compile "eu.davidea:flipview:1.1.2"
//ButterKnife
compile 'com.jakewharton:butterknife:8.0.1'
diff --git a/flexible-adapter-app/src/main/AndroidManifest.xml b/flexible-adapter-app/src/main/AndroidManifest.xml
index 93c46ccf..32cd7854 100644
--- a/flexible-adapter-app/src/main/AndroidManifest.xml
+++ b/flexible-adapter-app/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
-
+
@@ -20,7 +19,7 @@
-
+
@@ -28,6 +27,15 @@
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ExampleAdapter.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ExampleAdapter.java
index 9e1065a8..56db923a 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ExampleAdapter.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ExampleAdapter.java
@@ -1,15 +1,23 @@
package eu.davidea.samples.flexibleadapter;
-import android.support.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.view.ViewGroup;
import java.util.List;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.samples.flexibleadapter.models.LayoutItem;
-import eu.davidea.samples.flexibleadapter.models.ULSItem;
+import eu.davidea.flexibleadapter.utils.Utils;
+import eu.davidea.samples.flexibleadapter.items.ScrollableExpandableItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableFooterItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableLayoutItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableSubItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableULSItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
/**
@@ -26,36 +34,48 @@ public class ExampleAdapter extends FlexibleAdapter {
private static final String TAG = ExampleAdapter.class.getSimpleName();
- private AbstractFlexibleItem mUseCaseItem;
+ private Context mContext;
public ExampleAdapter(List items, Object listeners) {
- //true = Items implement hashCode() and have stableIds!
+ //stableIds ? true = Items implement hashCode() so they can have stableIds!
super(items, listeners, true);
+
+ //In case you need a Handler, do this:
+ //- Overrides the internal Handler with a custom callback that extends the internal one
+ mHandler = new Handler(Looper.getMainLooper(), new MyHandlerCallback());
}
@Override
public void updateDataSet(List items, boolean animate) {
- //NOTE: To have views/items not changed, set them into "items" before passing the final
+ // NOTE: To have views/items not changed, set them into "items" before passing the final
// list to the Adapter.
- //Overwrite the list and fully notify the change, pass false to not animate changes.
- //Watch out! The original list must a copy
+ // Overwrite the list and fully notify the change, pass false to not animate changes.
+ // Watch out! The original list must a copy.
super.updateDataSet(items, animate);
- //Add example view
- //showLayoutInfo(true);
+ // onPostUpdate() will automatically be called at the end of the Asynchronous update
+ // process. Manipulate the list inside that method only or you won't see the changes.
+ }
+
+ /*
+ * You can override this method to define your own concept of "Empty".
+ * This method is never called internally.
+ */
+ @Override
+ public boolean isEmpty() {
+ return super.isEmpty();
}
/*
* HEADER VIEW
- * This method show how to add Header/Footer View as it was for ListView.
- * The secret is the position! 0 for Header; itemCount for Footer ;-)
+ * This method shows how to add Header View as it was for ListView.
+ * Same Header item is enqueued for removal with a delay.
* The view is represented by a custom Item type to better represent any dynamic content.
*/
public void showLayoutInfo(boolean scrollToPosition) {
- if (!hasSearchText() && !isEmpty()) {
- //Define Example View
- final LayoutItem item = new LayoutItem("LAY-L");
+ if (!hasSearchText()) {
+ final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L");
if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
item.setId("LAY-S");
item.setTitle(mRecyclerView.getContext().getString(R.string.staggered_layout));
@@ -65,47 +85,81 @@ public void showLayoutInfo(boolean scrollToPosition) {
} else {
item.setTitle(mRecyclerView.getContext().getString(R.string.linear_layout));
}
- item.setSubtitle(mRecyclerView.getContext().getString(R.string.columns, getSpanCount(mRecyclerView.getLayoutManager())));
- addItemWithDelay((getItem(0) instanceof ULSItem ? 1 : 0), item, 0L,
- (!(getItem(0) instanceof ULSItem) && scrollToPosition));
- removeItemWithDelay(item, 4000L, true);
+ item.setSubtitle(mRecyclerView.getContext().getString(
+ R.string.columns,
+ String.valueOf(Utils.getSpanCount(mRecyclerView.getLayoutManager())))
+ );
+ // NOTE: If you have to change at runtime the LayoutManager AND add
+ // Scrollable Headers, consider to add them in post, using a delay >= 0
+ // otherwise scroll animations on all items will not start correctly.
+ addScrollableHeaderWithDelay(item, 1200L, scrollToPosition);
+ removeScrollableHeaderWithDelay(item, 4000L);
}
}
/*
* ANOTHER HEADER VIEW
- * This method show how to add Header/Footer View as it was for ListView.
- * The secret is the position! 0 for Header; itemCount for Footer ;-)
+ * This method showcases how to add a Header View with a delay.
* The view is represented by a custom Item type to better represent any dynamic content.
*/
public void addUserLearnedSelection(boolean scrollToPosition) {
- if (!DatabaseConfiguration.userLearnedSelection && !hasSearchText() && !(getItem(0) instanceof ULSItem)) {
- //Define Example View
- final ULSItem item = new ULSItem("ULS");
+ if (!DatabaseConfiguration.userLearnedSelection && !hasSearchText() && !(getItem(0) instanceof ScrollableULSItem)) {
+ final ScrollableULSItem item = new ScrollableULSItem("ULS");
item.setTitle(mRecyclerView.getContext().getString(R.string.uls_title));
item.setSubtitle(mRecyclerView.getContext().getString(R.string.uls_subtitle));
- addItemWithDelay(0, item, 1500L, scrollToPosition);
+ addScrollableHeaderWithDelay(item, 1000L, false);
}
}
- @Override
- public void filterItems(@NonNull List unfilteredItems) {
- super.filterItems(unfilteredItems);
- addUserLearnedSelection(false);
- showLayoutInfo(false);
+ /*
+ * FOOTER VIEW
+ * This method showcases how to delay add a Footer View.
+ * The view is represented by a custom Item type to better represent any dynamic content.
+ */
+ public void addScrollableFooter() {
+ final ScrollableFooterItem item = new ScrollableFooterItem("SFI");
+ item.setTitle(mRecyclerView.getContext().getString(R.string.scrollable_footer_title));
+ item.setSubtitle(mRecyclerView.getContext().getString(R.string.scrollable_footer_subtitle));
+ addScrollableFooterWithDelay(item, 1000L, false);
+ }
+
+ /*
+ * Showcase for EXPANDABLE HEADER VIEW
+ */
+ public void addScrollableExpandableAsHeader() {
+ final ScrollableExpandableItem expandable = new ScrollableExpandableItem("SEHI");
+ expandable.setTitle(mRecyclerView.getContext().getString(R.string.scrollable_expandable_header_title));
+ expandable.setSubtitle(mRecyclerView.getContext().getString(R.string.scrollable_expandable_header_subtitle));
+ expandable.addSubItem(new ScrollableSubItem("SEHI_1"));
+ expandable.addSubItem(new ScrollableSubItem("SEHI_2"));
+ addScrollableHeaderWithDelay(expandable, 1500L, false);
+ }
+
+ /*
+ * Showcase for EXPANDABLE FOOTER VIEW
+ */
+ public void addScrollableExpandableAsFooter() {
+ final ScrollableExpandableItem expandable = new ScrollableExpandableItem("SEFI");
+ expandable.setTitle(mRecyclerView.getContext().getString(R.string.scrollable_expandable_footer_title));
+ expandable.setSubtitle(mRecyclerView.getContext().getString(R.string.scrollable_expandable_footer_subtitle));
+ expandable.addSubItem(new ScrollableSubItem("SEFI_1"));
+ expandable.addSubItem(new ScrollableSubItem("SEFI_2"));
+ addScrollableFooterWithDelay(expandable, 1500L, false);
}
/**
* This is a customization of the Layout that hosts the header when sticky.
* The code works, but it is commented because not used (default is used).
+ * Note: You now can set a custom container by calling
+ * {@link #setStickyHeaderContainer(ViewGroup)}
*/
// @Override
-// public ViewGroup getStickySectionHeadersHolder() {
+// public ViewGroup getStickyHeaderContainer() {
// FrameLayout frameLayout = new FrameLayout(mRecyclerView.getContext());
// frameLayout.setLayoutParams(new ViewGroup.LayoutParams(
-// ViewGroup.LayoutParams.WRAP_CONTENT,//or MATCH_PARENT
+// ViewGroup.LayoutParams.WRAP_CONTENT, //or MATCH_PARENT
// ViewGroup.LayoutParams.WRAP_CONTENT));
-// ((ViewGroup) mRecyclerView.getParent()).addView(frameLayout);//This is important otherwise the Header disappears!
+// ((ViewGroup) mRecyclerView.getParent()).addView(frameLayout); //This is important otherwise the Header disappears!
// return (ViewGroup) mInflater.inflate(R.layout.sticky_header_layout, frameLayout);
// }
@@ -124,7 +178,7 @@ public void filterItems(@NonNull List unfilteredItems) {
*/
// @Override
// public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-// //Not implemented: METHOD A is used
+// // Not implemented: METHOD A is used
// }
/**
@@ -133,16 +187,51 @@ public void filterItems(@NonNull List unfilteredItems) {
*/
// @Override
// public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-// //Not implemented: METHOD A is used
+// // Not implemented: METHOD A is used
// }
-
@Override
public String onCreateBubbleText(int position) {
- if (!DatabaseConfiguration.userLearnedSelection && position == 0) {//This 'if' is for my example only
- //TODO FOR YOU: This is the normal line you should use: Usually it's the first letter
- return Integer.toString(position);
+ if (position < getScrollableHeaders().size()) {
+ return "Top";
+ } else if (position >= getItemCount() - getScrollableFooters().size()){
+ return "Bottom";
+ } else {
+ position -= getScrollableHeaders().size() + 1;
}
+ // TODO FOR YOU: The basic value, usually, is the first letter
+ // For me is the position
return super.onCreateBubbleText(position);
}
+ /**
+ * IMPORTANT: In order to preserve the internal calls, this custom Callback
+ * must extends {@link FlexibleAdapter.HandlerCallback}
+ * which implements {@link android.os.Handler.Callback},
+ * therefore you must call {@code super().handleMessage(message)}.
+ * This handler can launch asynchronous tasks.
+ * If you catch the reserved "what", keep in mind that this code should be executed
+ * before that task has been completed.
+ * Note: numbers 0-9 are reserved for the Adapter, use others for new values.
+ */
+ private class MyHandlerCallback extends HandlerCallback {
+ @Override
+ public boolean handleMessage(Message message) {
+ boolean done = super.handleMessage(message);
+ switch (message.what) {
+ // Currently reserved (you don't need to check these numbers!)
+ case 0: //async updateDataSet
+ case 1: //async filterItems
+ case 2: //confirm delete
+ case 8: //onLoadMore remove progress item
+ return done;
+
+ // Free to use, example:
+ case 10:
+ case 11:
+ return true;
+ }
+ return false;
+ }
+ }
+
}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/MainActivity.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/MainActivity.java
index 3ffcdbe5..4b569dc5 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/MainActivity.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/MainActivity.java
@@ -2,6 +2,8 @@
import android.app.SearchManager;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -11,6 +13,8 @@
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MenuItemCompat;
@@ -47,7 +51,6 @@
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IExpandable;
import eu.davidea.flexibleadapter.items.IFlexible;
-import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.samples.flexibleadapter.dialogs.EditItemDialog;
import eu.davidea.samples.flexibleadapter.dialogs.MessageDialog;
import eu.davidea.samples.flexibleadapter.fragments.AbstractFragment;
@@ -57,21 +60,23 @@
import eu.davidea.samples.flexibleadapter.fragments.FragmentExpandableMultiLevel;
import eu.davidea.samples.flexibleadapter.fragments.FragmentExpandableSections;
import eu.davidea.samples.flexibleadapter.fragments.FragmentHeadersSections;
+import eu.davidea.samples.flexibleadapter.fragments.FragmentHolderSections;
import eu.davidea.samples.flexibleadapter.fragments.FragmentInstagramHeaders;
import eu.davidea.samples.flexibleadapter.fragments.FragmentOverall;
import eu.davidea.samples.flexibleadapter.fragments.FragmentSelectionModes;
import eu.davidea.samples.flexibleadapter.fragments.FragmentStaggeredLayout;
import eu.davidea.samples.flexibleadapter.fragments.OnFragmentInteractionListener;
-import eu.davidea.samples.flexibleadapter.models.AbstractModelItem;
-import eu.davidea.samples.flexibleadapter.models.ExpandableItem;
-import eu.davidea.samples.flexibleadapter.models.HeaderItem;
-import eu.davidea.samples.flexibleadapter.models.OverallItem;
-import eu.davidea.samples.flexibleadapter.models.SimpleItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredItem;
-import eu.davidea.samples.flexibleadapter.models.SubItem;
+import eu.davidea.samples.flexibleadapter.items.AbstractItem;
+import eu.davidea.samples.flexibleadapter.items.ExpandableItem;
+import eu.davidea.samples.flexibleadapter.items.HeaderItem;
+import eu.davidea.samples.flexibleadapter.items.OverallItem;
+import eu.davidea.samples.flexibleadapter.items.SimpleItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredItem;
+import eu.davidea.samples.flexibleadapter.items.SubItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.samples.flexibleadapter.services.DatabaseType;
+import eu.davidea.samples.flexibleadapter.views.HeaderView;
import eu.davidea.utils.ScrollAwareFABBehavior;
import eu.davidea.utils.Utils;
@@ -79,9 +84,9 @@
* The Demo application is organized in Fragments with 1 Activity {@code MainActivity}
* implementing most of the methods. Each Fragment shows a different example and can assemble
* more functionalities at once.
- *
+ *
*
The Activity implementation is organized in this order:
- *
+ *
*
* - Activity management
*
- Initialization methods
@@ -94,10 +99,10 @@
*
- ActionMode implementation
*
- Extras
*
- *
+ *
* The Fragments may use Activity implementations or may override specific behaviors
* themselves. Fragments have {@code AbstractFragment} in common to have some methods reusable.
- *
+ *
*
...more on
* Demo app Wiki page.
*/
@@ -136,18 +141,17 @@ public class MainActivity extends AppCompatActivity implements
private NavigationView mNavigationView;
private AbstractFragment mFragment;
private SearchView mSearchView;
+
private final Handler mRefreshHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
public boolean handleMessage(Message message) {
switch (message.what) {
- case 0: //Stop
+ case 0: // Stop
mSwipeRefreshLayout.setRefreshing(false);
- mSwipeRefreshLayout.setEnabled(true);
return true;
- case 1: //Start
+ case 1: // Start
mSwipeRefreshLayout.setRefreshing(true);
- mSwipeRefreshLayout.setEnabled(false);
return true;
- case 2: //Show empty view
+ case 2: // Show empty view
ViewCompat.animate(findViewById(R.id.empty_view)).alpha(1);
return true;
default:
@@ -156,7 +160,9 @@ public boolean handleMessage(Message message) {
}
});
- /* ACTIVITY MANAGEMENT */
+ /* ===================
+ * ACTIVITY MANAGEMENT
+ * =================== */
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -171,15 +177,15 @@ protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
FlexibleAdapter.enableLogs(true);
- //Initialize Toolbar, Drawer & FAB
+ // Initialize Toolbar, Drawer & FAB
initializeToolbar();
initializeDrawer();
initializeFab();
- //Initialize Fragment containing Adapter & RecyclerView
+ // Initialize Fragment containing Adapter & RecyclerView
initializeFragment(savedInstanceState);
- //With FlexibleAdapter v5.0.0 we don't need to call this function anymore
- //It is automatically called if Activity implements FlexibleAdapter.OnUpdateListener
+ // With FlexibleAdapter v5.0.0 we don't need to call this function anymore
+ // It is automatically called if Activity implements FlexibleAdapter.OnUpdateListener
//updateEmptyView();
}
@@ -194,9 +200,9 @@ public void onSaveInstanceState(Bundle outState) {
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
- //Restore previous state
+ // Restore previous state
if (savedInstanceState != null && mAdapter != null) {
- //Selection
+ // Selection
mAdapter.onRestoreInstanceState(savedInstanceState);
mActionModeHelper.restoreSelection(this);
}
@@ -211,7 +217,9 @@ public void onFragmentChange(SwipeRefreshLayout swipeRefreshLayout, RecyclerView
initializeActionModeHelper(mode);
}
- /* INITIALIZATION METHODS */
+ /* ======================
+ * INITIALIZATION METHODS
+ * ====================== */
private void initializeActionModeHelper(int mode) {
mActionModeHelper = new ActionModeHelper(mAdapter, mFragment.getContextMenuResId(), this) {
@@ -239,20 +247,21 @@ private void initializeFragment(Bundle savedInstanceState) {
}
private void initializeSwipeToRefresh() {
- //Swipe down to force synchronize
+ // Swipe down to force synchronize
//mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
mSwipeRefreshLayout.setDistanceToTriggerSync(390);
- //mSwipeRefreshLayout.setEnabled(true); Controlled by fragments!
+ //mSwipeRefreshLayout.setEnabled(true); //Controlled by fragments!
mSwipeRefreshLayout.setColorSchemeResources(
android.R.color.holo_purple, android.R.color.holo_blue_light,
android.R.color.holo_green_light, android.R.color.holo_orange_light);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
- //Passing true as parameter we always animate the changes between the old and the new data set
+ // Passing true as parameter we always animate the changes between the old and the new data set
+ DatabaseService.getInstance().updateNewItems();
mAdapter.updateDataSet(DatabaseService.getInstance().getDatabaseList(), DatabaseConfiguration.animateOnUpdate);
- mSwipeRefreshLayout.setEnabled(false);
- mRefreshHandler.sendEmptyMessageDelayed(0, 100L);//Simulate network time
+ mSwipeRefreshLayout.setRefreshing(true);
+ mRefreshHandler.sendEmptyMessageDelayed(0, 1500L); //Simulate network time
mActionModeHelper.destroyActionModeIfCan();
}
});
@@ -264,7 +273,7 @@ private void initializeToolbar() {
mHeaderView = (HeaderView) findViewById(R.id.toolbar_header_view);
mHeaderView.bindTo(getString(R.string.app_name), getString(R.string.overall));
//mToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
- //Toolbar will now take on default Action Bar characteristics
+ // Toolbar will now take on default Action Bar characteristics
setSupportActionBar(mToolbar);
}
@@ -278,7 +287,7 @@ private void initializeDrawer() {
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
- //Version
+ // Version
TextView appVersion = (TextView) mNavigationView.getHeaderView(0).findViewById(R.id.app_version);
appVersion.setText(getString(R.string.about_version,
Utils.getVersionName(this),
@@ -294,7 +303,7 @@ public void onClick(View v) {
mFragment.performFabAction();
}
});
- //No Fab on 1st fragment
+ // No Fab on 1st fragment
hideFabSilently();
}
@@ -307,7 +316,9 @@ public void onFastScrollerStateChange(boolean scrolling) {
}
}
- /* NAVIGATION DRAWER & FRAGMENT MANAGEMENT */
+ /* =======================================
+ * NAVIGATION DRAWER & FRAGMENT MANAGEMENT
+ * ======================================= */
/**
* IMPORTANT!! READ THE COMMENT FOR THE FRAGMENT REPLACE
@@ -320,7 +331,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
ScrollAwareFABBehavior fabBehavior = ((ScrollAwareFABBehavior) layoutParams.getBehavior());
fabBehavior.setEnabled(false);
- //Handle navigation view item clicks
+ // Handle navigation view item clicks
int id = item.getItemId();
if (id == R.id.nav_overall) {
mFragment = FragmentOverall.newInstance(2);
@@ -343,6 +354,20 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
mFragment = FragmentExpandableSections.newInstance(3);
} else if (id == R.id.nav_staggered) {
mFragment = FragmentStaggeredLayout.newInstance(2);
+ } else if (id == R.id.nav_model_holders) {
+ mFragment = FragmentHolderSections.newInstance();
+ } else if (id == R.id.nav_viewpager) {
+ Intent intent = new Intent(this, ViewPagerActivity.class);
+ ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeBasic();
+ ActivityCompat.startActivity(this, intent, activityOptionsCompat.toBundle());
+ // Close drawer
+ mRecyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ mDrawer.closeDrawer(GravityCompat.START);
+ }
+ });
+ return true;
} else if (id == R.id.nav_about) {
MessageDialog.newInstance(
R.drawable.ic_info_grey600_24dp,
@@ -353,22 +378,25 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
.show(getFragmentManager(), MessageDialog.TAG);
return true;
} else if (id == R.id.nav_github) {
-
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("https://github.com/davideas/FlexibleAdapter"));
+ startActivity(Intent.createChooser(intent, getString(R.string.intent_chooser)));
+ return true;
}
// Insert the fragment by replacing any existing fragment
if (mFragment != null) {
- //Highlight the selected item has been done by NavigationView
+ // Highlight the selected item has been done by NavigationView
item.setChecked(true);
- //THIS IS VERY IMPORTANT. Because you are going to inflate a new RecyclerView, its
- //Adapter will be null, therefore the following method cannot be called automatically!
- //If your StickyHeaderContainer is in the main view, you must call this method to clean
- //the previous sticky view. Alternatively you can move the of StickyHeaderLayout
- //in the Fragment view.
+ // THIS IS VERY IMPORTANT. Because you are going to inflate a new RecyclerView, its
+ // Adapter will be null, therefore the following method cannot be called automatically!
+ // If your StickyHeaderContainer is in the main view, you must call this method to clean
+ // the previous sticky view. Alternatively you can move the of StickyHeaderLayout
+ // in the Fragment view.
mAdapter.onDetachedFromRecyclerView(mRecyclerView);
- //Inflate the new Fragment with the new RecyclerView and a new Adapter
+ // Inflate the new Fragment with the new RecyclerView and a new Adapter
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.recycler_view_container, mFragment).commit();
- //Close drawer
+ // Close drawer
mRecyclerView.post(new Runnable() {
@Override
public void run() {
@@ -384,7 +412,9 @@ public void run() {
return false;
}
- /* FLOATING ACTION BUTTON */
+ /* ======================
+ * FLOATING ACTION BUTTON
+ * ====================== */
private void hideFabSilently() {
mFab.setAlpha(0f);
@@ -409,11 +439,13 @@ private void showFab() {
}
}
- /* SEARCH VIEW */
+ /* ===========
+ * SEARCH VIEW
+ * =========== */
@Override
public void initSearchView(final Menu menu) {
- //Associate searchable configuration with the SearchView
+ // Associate searchable configuration with the SearchView
Log.d(TAG, "onCreateOptionsMenu setup SearchView!");
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.action_search);
@@ -452,11 +484,11 @@ public boolean onQueryTextChange(String newText) {
if (mAdapter.hasNewSearchText(newText)) {
Log.d(TAG, "onQueryTextChange newText: " + newText);
mAdapter.setSearchText(newText);
- //Fill and Filter mItems with your custom list and automatically animate the changes
- //Watch out! The original list must be a copy
+ // Fill and Filter mItems with your custom list and automatically animate the changes
+ // Watch out! The original list must be a copy
mAdapter.filterItems(DatabaseService.getInstance().getDatabaseList(), DatabaseConfiguration.delay);
}
- //Disable SwipeRefresh if search is active!!
+ // Disable SwipeRefresh if search is active!!
mSwipeRefreshLayout.setEnabled(!mAdapter.hasSearchText());
return true;
}
@@ -467,7 +499,9 @@ public boolean onQueryTextSubmit(String query) {
return onQueryTextChange(query);
}
- /* OPTION MENU PREPARATION & MANAGEMENT */
+ /* ====================================
+ * OPTION MENU PREPARATION & MANAGEMENT
+ * ==================================== */
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
@@ -487,37 +521,42 @@ public boolean onPrepareOptionsMenu(Menu menu) {
//mSearchView.setIconified(false);//This is not necessary
}
}
- //Fast Scroller
+ // Fast Scroller
MenuItem fastScrollerItem = menu.findItem(R.id.action_fast_scroller);
if (fastScrollerItem != null) {
fastScrollerItem.setChecked(mAdapter.isFastScrollerEnabled());
}
- //Animate on update?
+ // Animate on update?
MenuItem animateUpdateMenuItem = menu.findItem(R.id.action_animate_on_update);
if (animateUpdateMenuItem != null) {
animateUpdateMenuItem.setChecked(DatabaseConfiguration.animateOnUpdate);
}
- //Headers are shown?
+ // Headers are shown?
MenuItem headersMenuItem = menu.findItem(R.id.action_show_hide_headers);
if (headersMenuItem != null) {
headersMenuItem.setTitle(mAdapter.areHeadersShown() ? R.string.hide_headers : R.string.show_headers);
}
- //Sticky Header item?
+ // Sticky Header item?
MenuItem stickyItem = menu.findItem(R.id.action_sticky_headers);
if (stickyItem != null) {
stickyItem.setEnabled(mAdapter.areHeadersShown());
stickyItem.setChecked(mAdapter.areHeadersSticky());
}
- //Scrolling Animations?
+ // Scrolling Animations?
MenuItem animationMenuItem = menu.findItem(R.id.action_animation);
if (animationMenuItem != null) {
animationMenuItem.setChecked(DatabaseConfiguration.animateOnScrolling);
}
- //Reverse scrolling animation?
+ // Reverse scrolling animation?
MenuItem reverseMenuItem = menu.findItem(R.id.action_reverse);
if (reverseMenuItem != null) {
reverseMenuItem.setEnabled(mAdapter.isAnimationOnScrollingEnabled());
- reverseMenuItem.setChecked(mAdapter.isAnimationOnReverseScrolling());
+ reverseMenuItem.setChecked(mAdapter.isAnimationOnReverseScrollingEnabled());
+ }
+ // DiffUtil?
+ MenuItem diffUtilItem = menu.findItem(R.id.action_diff_util);
+ if (diffUtilItem != null) {
+ diffUtilItem.setChecked(DatabaseConfiguration.animateWithDiffUtil);
}
return super.onPrepareOptionsMenu(menu);
}
@@ -535,15 +574,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
DatabaseConfiguration.animateOnScrolling = false;
mAdapter.setAnimationOnScrolling(false);
item.setChecked(false);
- Snackbar.make(findViewById(R.id.main_view), "Disabled scrolling animation, now reopen the page\n(P = persistent)", Snackbar.LENGTH_SHORT).show();
+ Snackbar.make(findViewById(R.id.main_view), "Disabled scrolling animation, now reopen the page\n(* = persistent)", Snackbar.LENGTH_SHORT).show();
} else {
DatabaseConfiguration.animateOnScrolling = true;
mAdapter.setAnimationOnScrolling(true);
item.setChecked(true);
- Snackbar.make(findViewById(R.id.main_view), "Enabled scrolling animation, now reopen the page\n(P = persistent)", Snackbar.LENGTH_SHORT).show();
+ Snackbar.make(findViewById(R.id.main_view), "Enabled scrolling animation, now reopen the page\n(* = persistent)", Snackbar.LENGTH_SHORT).show();
}
} else if (id == R.id.action_reverse) {
- if (mAdapter.isAnimationOnReverseScrolling()) {
+ if (mAdapter.isAnimationOnReverseScrollingEnabled()) {
mAdapter.setAnimationOnReverseScrolling(false);
item.setChecked(false);
Snackbar.make(findViewById(R.id.main_view), "Disabled reverse scrolling animation", Snackbar.LENGTH_SHORT).show();
@@ -552,6 +591,18 @@ public boolean onOptionsItemSelected(MenuItem item) {
item.setChecked(true);
Snackbar.make(findViewById(R.id.main_view), "Enabled reverse scrolling animation", Snackbar.LENGTH_SHORT).show();
}
+ } else if (id == R.id.action_diff_util) {
+ if (mAdapter.isAnimateChangesWithDiffUtil()) {
+ DatabaseConfiguration.animateWithDiffUtil = false;
+ mAdapter.setAnimateChangesWithDiffUtil(false);
+ item.setChecked(false);
+ Snackbar.make(findViewById(R.id.main_view), "Default calculation is used to animate changes\n(* = persistent)", Snackbar.LENGTH_SHORT).show();
+ } else {
+ DatabaseConfiguration.animateWithDiffUtil = true;
+ mAdapter.setAnimateChangesWithDiffUtil(true);
+ item.setChecked(true);
+ Snackbar.make(findViewById(R.id.main_view), "DiffUtil is used to animate changes\n(* = persistent)", Snackbar.LENGTH_SHORT).show();
+ }
} else if (id == R.id.action_auto_collapse) {
if (mAdapter.isAutoCollapseOnExpand()) {
mAdapter.setAutoCollapseOnExpand(false);
@@ -581,15 +632,10 @@ public boolean onOptionsItemSelected(MenuItem item) {
item.setTitle(R.string.hide_headers);
}
} else if (id == R.id.action_sticky_headers) {
- if (mAdapter.areHeadersSticky()) {
- mAdapter.disableStickyHeaders();
- item.setChecked(false);
- Snackbar.make(findViewById(R.id.main_view), "Sticky headers disabled", Snackbar.LENGTH_SHORT).show();
- } else {
- mAdapter.enableStickyHeaders();
- item.setChecked(true);
- Snackbar.make(findViewById(R.id.main_view), "Sticky headers enabled", Snackbar.LENGTH_SHORT).show();
- }
+ mAdapter.setStickyHeaders(!mAdapter.areHeadersSticky());
+ item.setChecked(!mAdapter.areHeadersSticky());
+ Snackbar.make(findViewById(R.id.main_view), "Sticky headers " +
+ (mAdapter.areHeadersSticky() ? "disabled" : "enabled"), Snackbar.LENGTH_SHORT).show();
} else if (id == R.id.action_selection_mode) {
if (mAdapter.getMode() == SelectableAdapter.MODE_IDLE) {
mAdapter.setMode(SelectableAdapter.MODE_SINGLE);
@@ -614,15 +660,16 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
- /* DIALOG LISTENER IMPLEMENTATION (For the example of onItemClick) */
-
+ /* ===============================================================
+ * DIALOG LISTENER IMPLEMENTATION (For the example of onItemClick)
+ * =============================================================== */
@Override
public void onTitleModified(int position, String newTitle) {
AbstractFlexibleItem abstractItem = mAdapter.getItem(position);
assert abstractItem != null;
- if (abstractItem instanceof AbstractModelItem) {
- AbstractModelItem exampleItem = (AbstractModelItem) abstractItem;
+ if (abstractItem instanceof AbstractItem) {
+ AbstractItem exampleItem = (AbstractItem) abstractItem;
exampleItem.setTitle(newTitle);
} else if (abstractItem instanceof HeaderItem) {
HeaderItem headerItem = (HeaderItem) abstractItem;
@@ -631,7 +678,11 @@ public void onTitleModified(int position, String newTitle) {
mAdapter.updateItem(position, abstractItem, null);
}
- /* FLEXIBLE ADAPTER LISTENERS IMPLEMENTATION */
+ /* ========================================================================
+ * FLEXIBLE ADAPTER LISTENERS IMPLEMENTATION
+ * Listeners implementation are in MainActivity to easily reuse the common
+ * components like SwipeToRefresh, ActionMode, NavigationView, etc...
+ * ======================================================================== */
@Override
public boolean onItemClick(int position) {
@@ -643,13 +694,14 @@ public boolean onItemClick(int position) {
return false;
}
- //Action on elements are allowed if Mode is IDLE, otherwise selection has priority
+ // Action on elements are allowed if Mode is IDLE, otherwise selection has priority
if (mAdapter.getMode() != SelectableAdapter.MODE_IDLE && mActionModeHelper != null) {
- return mActionModeHelper.onClick(position);
+ boolean activate = mActionModeHelper.onClick(position);
+ Log.d(TAG, "Last activated position " + mActionModeHelper.getActivatedPosition());
+ return activate;
} else {
- //Notify the active callbacks or implement a custom action onClick
- if (!(flexibleItem instanceof ExpandableItem) && flexibleItem instanceof SimpleItem
- || flexibleItem instanceof SubItem) {
+ // Notify the active callbacks or implement a custom action onClick
+ if (flexibleItem instanceof SimpleItem || flexibleItem instanceof SubItem) {
//TODO FOR YOU: call your custom Action on item click
String title = extractTitleFrom(flexibleItem);
EditItemDialog.newInstance(title, position).show(getFragmentManager(), EditItemDialog.TAG);
@@ -687,54 +739,65 @@ public void onItemSwipe(final int position, int direction) {
Log.i(TAG, "onItemSwipe position=" + position +
" direction=" + (direction == ItemTouchHelper.LEFT ? "LEFT" : "RIGHT"));
- //Option 1 FULL_SWIPE: Direct action no Undo Action
- //Do something based on direction when item has been swiped:
+ // Option 1 FULL_SWIPE: Direct action no Undo Action
+ // Do something based on direction when item has been swiped:
// A) update item, set "read" if an email etc.
// B) remove the item from the adapter;
- //Option 2 FULL_SWIPE: Delayed action with Undo Action
- //Show action button and start a new Handler:
+ // Option 2 FULL_SWIPE: Delayed action with Undo Action
+ // Show action button and start a new Handler:
// A) on time out do something based on direction (open dialog with options);
- //Create list for single position (only in onItemSwipe)
+ // Create list for single position (only in onItemSwipe)
List positions = new ArrayList<>(1);
positions.add(position);
- //Build the message
+ // Build the message
IFlexible abstractItem = mAdapter.getItem(position);
StringBuilder message = new StringBuilder();
message.append(extractTitleFrom(abstractItem)).append(" ");
- //Experimenting NEW feature
+ // Experimenting NEW feature
if (abstractItem.isSelectable())
mAdapter.setRestoreSelectionOnUndo(false);
- //Perform different actions
- //Here, option 2A) is implemented
+ // Perform different actions
+ // Here, option 2A) is implemented
if (direction == ItemTouchHelper.LEFT) {
message.append(getString(R.string.action_archived));
+
+ // Example of UNDO color
+ int actionTextColor;
+ if (Utils.hasMarshmallow()) {
+ actionTextColor = getColor(R.color.material_color_orange_500);
+ } else {
+ //noinspection deprecation
+ actionTextColor = getResources().getColor(R.color.material_color_orange_500);
+ }
+
new UndoHelper(mAdapter, this)
- .withPayload(null)//You can pass any custom object (in this case Boolean is enough)
+ .withPayload(null) //You can pass any custom object (in this case Boolean is enough)
.withAction(UndoHelper.ACTION_UPDATE, new UndoHelper.SimpleActionListener() {
@Override
public boolean onPreAction() {
- //Return true to avoid default immediate deletion.
- //Ask to the user what to do, open a custom dialog. On option chosen,
- //remove the item from Adapter list as usual.
+ // Return true to avoid default immediate deletion.
+ // Ask to the user what to do, open a custom dialog. On option chosen,
+ // remove the item from Adapter list as usual.
return true;
}
})
+ .withActionTextColor(actionTextColor)
.remove(positions, findViewById(R.id.main_view), message,
getString(R.string.undo), UndoHelper.UNDO_TIMEOUT);
//Here, option 1B) is implemented
} else if (direction == ItemTouchHelper.RIGHT) {
message.append(getString(R.string.action_deleted));
+ mSwipeRefreshLayout.setRefreshing(true);
new UndoHelper(mAdapter, this)
- .withPayload(null)//You can pass any custom object (in this case Boolean is enough)
+ .withPayload(null) //You can pass any custom object (in this case Boolean is enough)
.withAction(UndoHelper.ACTION_REMOVE, new UndoHelper.SimpleActionListener() {
@Override
public void onPostAction() {
- logOrphanHeaders();
- //Handle ActionMode title
+ // Handle ActionMode title
if (mAdapter.getSelectedItemCount() == 0)
mActionModeHelper.destroyActionModeIfCan();
else
@@ -757,7 +820,8 @@ public void onUpdateEmptyView(int size) {
FastScroller fastScroller = (FastScroller) findViewById(R.id.fast_scroller);
View emptyView = findViewById(R.id.empty_view);
TextView emptyText = (TextView) findViewById(R.id.empty_text);
- emptyText.setText(getString(R.string.no_items));
+ if (emptyText != null)
+ emptyText.setText(getString(R.string.no_items));
if (size > 0) {
fastScroller.setVisibility(View.VISIBLE);
mRefreshHandler.removeMessages(2);
@@ -767,15 +831,17 @@ public void onUpdateEmptyView(int size) {
mRefreshHandler.sendEmptyMessage(2);
fastScroller.setVisibility(View.GONE);
}
- if (mAdapter != null && mAdapter.hasSearchText()) {
- Snackbar.make(findViewById(R.id.main_view), "Filtered " + size + " items", Snackbar.LENGTH_SHORT).show();
+ if (mAdapter != null) {
+ String message = (mAdapter.hasSearchText() ? "Filtered " : "Refreshed ");
+ message += size + " items in " + mAdapter.getTime() + "ms";
+ Snackbar.make(findViewById(R.id.main_view), message, Snackbar.LENGTH_SHORT).show();
}
}
@Override
public void onUndoConfirmed(int action) {
if (action == UndoHelper.ACTION_UPDATE) {
- //FIXME: Adjust click animation on swiped item
+ //TODO: Complete click animation on swiped item
// final RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForLayoutPosition(mSwipedPosition);
// if (holder instanceof ItemTouchHelperCallback.ViewHolderCallback) {
// final View view = ((ItemTouchHelperCallback.ViewHolderCallback) holder).getFrontView();
@@ -789,11 +855,11 @@ public void onUndoConfirmed(int action) {
// animator.start();
// }
} else if (action == UndoHelper.ACTION_REMOVE) {
- //Custom action is restore deleted items
+ // Custom action is restore deleted items
mAdapter.restoreDeletedItems();
- //Enable SwipeRefresh
- mRefreshHandler.sendEmptyMessage(0);
- //Check also selection restoration
+ // Disable Refreshing
+ mSwipeRefreshLayout.setRefreshing(false);
+ // Check also selection restoration
if (mAdapter.isRestoreWithSelection()) {
mActionModeHelper.restoreSelection(this);
}
@@ -802,12 +868,12 @@ public void onUndoConfirmed(int action) {
@Override
public void onDeleteConfirmed(int action) {
- //Enable SwipeRefresh
- mRefreshHandler.sendEmptyMessage(0);
- //Removing items from Database. Example:
+ // Disable Refreshing
+ mSwipeRefreshLayout.setRefreshing(false);
+ // Removing items from Database. Example:
for (AbstractFlexibleItem adapterItem : mAdapter.getDeletedItems()) {
try {
- //NEW! You can take advantage of AutoMap and differentiate logic by viewType using "switch" statement
+ // NEW! You can take advantage of AutoMap and differentiate logic by viewType using "switch" statement
switch (adapterItem.getLayoutRes()) {
case R.layout.recycler_sub_item:
SubItem subItem = (SubItem) adapterItem;
@@ -821,15 +887,14 @@ public void onDeleteConfirmed(int action) {
}
} catch (IllegalStateException e) {
- //AutoMap is disabled, fallback to if-else with "instanceof" statement
+ // AutoMap is disabled, fallback to if-else with "instanceof" statement
if (adapterItem instanceof SubItem) {
- //SubItem
+ // SubItem
SubItem subItem = (SubItem) adapterItem;
IExpandable expandable = mAdapter.getExpandableOf(subItem);
DatabaseService.getInstance().removeSubItem(expandable, subItem);
Log.d(TAG, "Confirm removed " + subItem.getTitle());
- } else if (adapterItem instanceof SimpleItem) {
- //SimpleItem or ExpandableItem(extends SimpleItem)
+ } else if (adapterItem instanceof SimpleItem || adapterItem instanceof ExpandableItem) {
DatabaseService.getInstance().removeItem(adapterItem);
Log.d(TAG, "Confirm removed " + adapterItem);
}
@@ -837,7 +902,9 @@ public void onDeleteConfirmed(int action) {
}
}
- /* ACTION MODE IMPLEMENTATION */
+ /* ==========================
+ * ACTION MODE IMPLEMENTATION
+ * ========================== */
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@@ -861,11 +928,11 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
case R.id.action_select_all:
mAdapter.selectAll();
mActionModeHelper.updateContextTitle(mAdapter.getSelectedItemCount());
- //We consume the event
+ // We consume the event
return true;
case R.id.action_delete:
- //Build message before delete, for the SnackBar
+ // Build message before delete, for the SnackBar
StringBuilder message = new StringBuilder();
message.append(getString(R.string.action_deleted)).append(" ");
for (Integer pos : mAdapter.getSelectedPositions()) {
@@ -874,57 +941,56 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
message.append(", ");
}
- //Experimenting NEW feature
+ // Experimenting NEW feature
mAdapter.setRestoreSelectionOnUndo(true);
- //New Undo Helper
+ // New Undo Helper
new UndoHelper(mAdapter, this)
.withPayload(Payload.CHANGE)
.withAction(UndoHelper.ACTION_REMOVE, new UndoHelper.OnActionListener() {
@Override
public boolean onPreAction() {
- //Don't consume the event
- //OR use UndoHelper.SimpleActionListener and Override only onPostAction()
+ // Don't consume the event
+ // OR use UndoHelper.SimpleActionListener and Override only onPostAction()
return false;
}
@Override
public void onPostAction() {
- //Disable SwipeRefresh
+ // Enable Refreshing
mRefreshHandler.sendEmptyMessage(1);
- mRefreshHandler.sendEmptyMessageDelayed(0, 20000);
- //Finish the action mode
+ mRefreshHandler.sendEmptyMessageDelayed(0, 7000);
+ // Finish the action mode
mActionModeHelper.destroyActionModeIfCan();
- logOrphanHeaders();
}
})
.remove(mAdapter.getSelectedPositions(),
findViewById(R.id.main_view), message,
- getString(R.string.undo), 20000);
+ getString(R.string.undo), 7000);
- //We consume the event
+ // We consume the event
return true;
case R.id.action_merge:
if (mAdapter.getSelectedItemCount() > 1) {
- //Selected positions are sorted by default, we take the first item of the set
+ // Selected positions are sorted by default, we take the first item of the set
int mainPosition = mAdapter.getSelectedPositions().get(0);
mAdapter.removeSelection(mainPosition);
StaggeredItem mainItem = (StaggeredItem) mAdapter.getItem(mainPosition);
for (Integer position : mAdapter.getSelectedPositions()) {
- //Merge item - Save the modification in the memory for next refresh
+ // Merge item - Save the modification in the memory for next refresh
DatabaseService.getInstance().mergeItem(mainItem, (StaggeredItem) mAdapter.getItem(position));
}
- //Remove merged item from the list
+ // Remove merged item from the list
mAdapter.removeAllSelectedItems();
- //Keep selection on mainItem & Skip default notification by calling addSelection
+ // Keep selection on mainItem & Skip default notification by calling addSelection
mAdapter.addSelection(mainPosition);
- //Custom notification to bind again (ripple only)
+ // Custom notification to bind again (ripple only)
mAdapter.notifyItemChanged(mainPosition, "blink");
- //New title for context
+ // New title for context
mActionModeHelper.updateContextTitle(mAdapter.getSelectedItemCount());
}
- //We consume always the event, never finish the ActionMode
+ // We consume always the event, never finish the ActionMode
return true;
case R.id.action_split:
@@ -933,25 +999,25 @@ public void onPostAction() {
if (mainItem.getMergedItems() != null) {
List itemsToSplit = new ArrayList<>(mainItem.getMergedItems());
for (StaggeredItem itemToSplit : itemsToSplit) {
- //Split item - Save the modification in the memory for next refresh
+ // Split item - Save the modification in the memory for next refresh
DatabaseService.getInstance().splitItem(mainItem, itemToSplit);
- //We know the section object, so we can insert directly the item at the right position
- //The calculated position is then returned
+ // We know the section object, so we can insert directly the item at the right position
+ // The calculated position is then returned
int position = mAdapter.addItemToSection(itemToSplit, mainItem.getHeader(), new DatabaseService.ItemComparatorById());
- mAdapter.toggleSelection(position);//Execute default notification
+ mAdapter.toggleSelection(position); //Execute default notification
mAdapter.notifyItemChanged(position, "blink");
}
- //Custom notification to bind again (ripple only)
+ // Custom notification to bind again (ripple only)
mAdapter.notifyItemChanged(mAdapter.getGlobalPositionOf(mainItem), "blink");
- //New title for context
+ // New title for context
mActionModeHelper.updateContextTitle(mAdapter.getSelectedItemCount());
}
}
- //We consume always the event, never finish the ActionMode
+ // We consume always the event, never finish the ActionMode
return true;
default:
- //If an item is not implemented we don't consume the event, so we finish the ActionMode
+ // If an item is not implemented we don't consume the event, so we finish the ActionMode
return false;
}
}
@@ -966,44 +1032,38 @@ public void onDestroyActionMode(ActionMode mode) {
}
}
- /* EXTRAS */
+ /* ======
+ * EXTRAS
+ * ====== */
@Override
public void onBackPressed() {
- //If Drawer is open, back key closes it
+ // If Drawer is open, back key closes it
if (mDrawer.isDrawerOpen(GravityCompat.START)) {
mDrawer.closeDrawer(GravityCompat.START);
return;
}
- //If ActionMode is active, back key closes it
+ // If ActionMode is active, back key closes it
if (mActionModeHelper.destroyActionModeIfCan()) return;
- //If SearchView is visible, back key cancels search and iconify it
+ // If SearchView is visible, back key cancels search and iconify it
if (mSearchView != null && !mSearchView.isIconified()) {
mSearchView.setIconified(true);
return;
}
- //Return to Overall View
+ // Return to Overall View
if (DatabaseService.getInstance().getDatabaseType() != DatabaseType.OVERALL) {
MenuItem menuItem = mNavigationView.getMenu().findItem(R.id.nav_overall);
onNavigationItemSelected(menuItem);
return;
}
- //Close the App
+ // Close the App
DatabaseService.onDestroy();
super.onBackPressed();
}
- private void logOrphanHeaders() {
- //If removeOrphanHeader is set false, once hidden the Orphan Headers are not shown
- // anymore, but you can recover them using getOrphanHeaders()
- for (IHeader header : mAdapter.getOrphanHeaders()) {
- Log.w(TAG, "Logging orphan header " + header);
- }
- }
-
private String extractTitleFrom(IFlexible flexibleItem) {
- if (flexibleItem instanceof AbstractModelItem) {
- AbstractModelItem exampleItem = (AbstractModelItem) flexibleItem;
+ if (flexibleItem instanceof AbstractItem) {
+ AbstractItem exampleItem = (AbstractItem) flexibleItem;
String title = exampleItem.getTitle();
if (exampleItem instanceof ExpandableItem) {
ExpandableItem expandableItem = (ExpandableItem) flexibleItem;
@@ -1016,7 +1076,7 @@ private String extractTitleFrom(IFlexible flexibleItem) {
HeaderItem headerItem = (HeaderItem) flexibleItem;
return headerItem.getTitle();
}
- //We already covered all situations with instanceof
+ // We already covered all situations with instanceof
return "";
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/OverallAdapter.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/OverallAdapter.java
index 1661dce8..89aa28a4 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/OverallAdapter.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/OverallAdapter.java
@@ -1,6 +1,7 @@
package eu.davidea.samples.flexibleadapter;
import android.app.Activity;
+import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
@@ -13,8 +14,10 @@
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IFlexible;
-import eu.davidea.samples.flexibleadapter.models.LayoutItem;
-import eu.davidea.samples.flexibleadapter.models.OverallItem;
+import eu.davidea.flexibleadapter.utils.DrawableUtils;
+import eu.davidea.samples.flexibleadapter.items.ScrollableLayoutItem;
+import eu.davidea.samples.flexibleadapter.items.OverallItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.utils.Utils;
@@ -47,7 +50,7 @@ public OverallAdapter(Activity activity) {
public void showLayoutInfo(boolean scrollToPosition) {
if (!hasSearchText()) {
//Define Example View
- final LayoutItem item = new LayoutItem("LAY-L");
+ final ScrollableLayoutItem item = new ScrollableLayoutItem("LAY-L");
if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
item.setId("LAY-S");
item.setTitle(mRecyclerView.getContext().getString(R.string.staggered_layout));
@@ -57,9 +60,12 @@ public void showLayoutInfo(boolean scrollToPosition) {
} else {
item.setTitle(mRecyclerView.getContext().getString(R.string.linear_layout));
}
- item.setSubtitle(mRecyclerView.getContext().getString(R.string.columns, getSpanCount(mRecyclerView.getLayoutManager())));
- addItemWithDelay(0, item, 500L, scrollToPosition);
- removeItemWithDelay(item, 2000L, true);
+ item.setSubtitle(mRecyclerView.getContext().getString(
+ R.string.columns,
+ String.valueOf(eu.davidea.flexibleadapter.utils.Utils.getSpanCount(mRecyclerView.getLayoutManager())))
+ );
+ addScrollableHeaderWithDelay(item, 500L, scrollToPosition);
+ removeScrollableHeaderWithDelay(item, 3000L);
}
}
@@ -70,8 +76,9 @@ public void showLayoutInfo(boolean scrollToPosition) {
@Override
public int getItemViewType(int position) {
IFlexible item = getItem(position);
- if (item instanceof LayoutItem) return R.layout.recycler_layout_item;
- else return R.layout.recycler_label_item;
+ if (item instanceof ScrollableUseCaseItem) return R.layout.recycler_scrollable_usecase_item;
+ else if (item instanceof ScrollableLayoutItem) return R.layout.recycler_scrollable_layout_item;
+ else return R.layout.recycler_overall_item;
}
/**
@@ -82,8 +89,12 @@ public int getItemViewType(int position) {
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mInflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
- case R.layout.recycler_layout_item:
- return new LayoutItem.ExampleViewHolder(
+ case R.layout.recycler_scrollable_usecase_item:
+ return new ScrollableUseCaseItem.UCViewHolder(
+ mInflater.inflate(viewType, parent, false), this);
+
+ case R.layout.recycler_scrollable_layout_item:
+ return new ScrollableLayoutItem.LayoutViewHolder(
mInflater.inflate(viewType, parent, false), this);
default:
return new OverallItem.LabelViewHolder(
@@ -94,13 +105,36 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType
/**
* METHOD A - NEW! Via Model objects. In this case you don't need to implement this method!
* METHOD B - You override and implement this method as you prefer (don't call super).
+ *
+ * Using Method B, some methods need to be called by the user, see bottom of this method!
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payload) {
int viewType = getItemViewType(position);
- if (viewType == R.layout.recycler_layout_item) {
- LayoutItem item = (LayoutItem) getItem(position);
- LayoutItem.ExampleViewHolder vHolder = (LayoutItem.ExampleViewHolder) holder;
+ Context context = holder.itemView.getContext();
+
+
+ if (viewType == R.layout.recycler_scrollable_usecase_item) {
+ ScrollableUseCaseItem item = (ScrollableUseCaseItem) getItem(position);
+ ScrollableUseCaseItem.UCViewHolder vHolder = (ScrollableUseCaseItem.UCViewHolder) holder;
+ assert item != null;
+
+ DrawableUtils.setBackgroundCompat(holder.itemView, DrawableUtils.getRippleDrawable(
+ DrawableUtils.getColorDrawable(context.getResources().getColor(R.color.material_color_blue_grey_50)),
+ DrawableUtils.getColorControlHighlight(context))
+ );
+ vHolder.mTitle.setText(Utils.fromHtmlCompat(item.getTitle()));
+ vHolder.mSubtitle.setText(Utils.fromHtmlCompat(item.getSubtitle()));
+
+ //Support for StaggeredGridLayoutManager
+ if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams()).setFullSpan(true);
+ Log.d("LayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout");
+ }
+
+ } else if (viewType == R.layout.recycler_scrollable_layout_item) {
+ ScrollableLayoutItem item = (ScrollableLayoutItem) getItem(position);
+ ScrollableLayoutItem.LayoutViewHolder vHolder = (ScrollableLayoutItem.LayoutViewHolder) holder;
assert item != null;
vHolder.mTitle.setSelected(true);//For marquee
@@ -113,7 +147,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List
Log.d("LayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout");
}
- } else if (viewType == R.layout.recycler_label_item) {
+ } else if (viewType == R.layout.recycler_overall_item) {
OverallItem item = (OverallItem) getItem(position);
OverallItem.LabelViewHolder vHolder = (OverallItem.LabelViewHolder) holder;
assert item != null;
@@ -130,6 +164,14 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List
vHolder.mIcon.setImageDrawable(item.getIcon());
}
}
+
+ // IMPORTANT!!!
+ // With method B, animateView() needs to be called by the user!
+ // With method A, the call is handled by the Adapter
+ animateView(holder, position);
+ // Same concept for EndlessScrolling and View activation:
+ // - onLoadMore(position);
+ // - holder.itemView.setActivated(isSelected(position));
}
}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ViewPagerActivity.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ViewPagerActivity.java
new file mode 100644
index 00000000..955a7975
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ViewPagerActivity.java
@@ -0,0 +1,143 @@
+package eu.davidea.samples.flexibleadapter;
+
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.OnApplyWindowInsetsListener;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.WindowInsetsCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import eu.davidea.samples.flexibleadapter.fragments.FragmentViewPager;
+import eu.davidea.samples.flexibleadapter.views.HeaderView;
+
+public class ViewPagerActivity extends AppCompatActivity {
+
+ /**
+ * The {@link android.support.v4.view.PagerAdapter} that will provide
+ * fragments for each of the sections. We use a
+ * {@link FragmentPagerAdapter} derivative, which will keep every
+ * loaded fragment in memory. If this becomes too memory intensive, it
+ * may be best to switch to a
+ * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ */
+ private SectionsPagerAdapter mSectionsPagerAdapter;
+
+ /**
+ * The {@link ViewPager} that will host the section contents.
+ */
+ private ViewPager mViewPager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view_pager);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ HeaderView headerView = (HeaderView) findViewById(R.id.toolbar_header_view);
+ headerView.bindTo(getString(R.string.app_name), getString(R.string.viewpager));
+
+ // Create the adapter that will return a fragment for each of the three
+ // primary sections of the activity.
+ mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
+
+ // Set up the ViewPager with the sections adapter.
+ mViewPager = (ViewPager) findViewById(R.id.view_pager);
+ mViewPager.setAdapter(mSectionsPagerAdapter);
+
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
+ tabLayout.setupWithViewPager(mViewPager);
+
+ //Coordinatorlayout Status Bar Padding Disappears From Viewpager 2nd-page
+ //http://stackoverflow.com/questions/31368781/coordinatorlayout-status-bar-padding-disappears-from-viewpager-2nd-page
+ ViewCompat.setOnApplyWindowInsetsListener(mViewPager, new OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsetsCompat onApplyWindowInsets(View v,
+ WindowInsetsCompat insets) {
+ insets = ViewCompat.onApplyWindowInsets(v, insets);
+ if (insets.isConsumed()) {
+ return insets;
+ }
+
+ boolean consumed = false;
+ for (int i = 0, count = mViewPager.getChildCount(); i < count; i++) {
+ ViewCompat.dispatchApplyWindowInsets(mViewPager.getChildAt(i), insets);
+ if (insets.isConsumed()) {
+ consumed = true;
+ }
+ }
+ return consumed ? insets.consumeSystemWindowInsets() : insets;
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_view_pager, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ if (id == android.R.id.home) {
+ super.onBackPressed();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
+ * one of the sections/tabs/pages.
+ */
+ public class SectionsPagerAdapter extends FragmentPagerAdapter {
+
+ public SectionsPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ // getItem is called to instantiate the fragment for the given page.
+ // Return a FragmentViewPager (defined as a static inner class below).
+ return FragmentViewPager.newInstance(position + 1);
+ }
+
+ @Override
+ public int getCount() {
+ // Show 3 total pages.
+ return 3;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ switch (position) {
+ case 0:
+ return "SECTION 1";
+ case 1:
+ return "SECTION 2";
+ case 2:
+ return "SECTION 3";
+ }
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAnimators.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAnimators.java
index 7c91a01f..39aa0b79 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAnimators.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAnimators.java
@@ -37,6 +37,7 @@
import eu.davidea.samples.flexibleadapter.animators.SlideInLeftAnimator;
import eu.davidea.samples.flexibleadapter.animators.SlideInRightAnimator;
import eu.davidea.samples.flexibleadapter.animators.SlideInUpAnimator;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
@@ -67,29 +68,29 @@ public FragmentAnimators() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
- DatabaseService.getInstance().createAnimatorsDatabase(20);//N. of sections
+ // Create New Database and Initialize RecyclerView
+ DatabaseService.getInstance().createAnimatorsDatabase(20); //N. of sections
initializeRecyclerView(savedInstanceState);
- //Restore FAB button and icon
+ // Restore FAB button and icon
initializeFab();
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mAdapter.expandItemsAtStartUp()
.setAutoCollapseOnExpand(false)
.setAutoScrollOnExpand(true)
.setOnlyEntryAnimation(false)
- .setAnimationEntryStep(true)//In Overall, watch the effect at initial loading when Grid Layout is set
+ .setAnimationEntryStep(true) //In Overall, watch the effect at initial loading when Grid Layout is set
.setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling)
.setAnimationOnReverseScrolling(true)
.setAnimationInterpolator(new DecelerateInterpolator())
@@ -99,24 +100,26 @@ private void initializeRecyclerView(Bundle savedInstanceState) {
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Custom item animators inherit 'canReuseUpdatedViewHolder()' from Default Item
+ // NOTE: Custom item animators inherit 'canReuseUpdatedViewHolder()' from Default Item
// Animator. It will return true if a Payload is provided. FlexibleAdapter is actually
// sending Payloads onItemChange notifications.
mRecyclerView.setItemAnimator(new FlexibleItemAnimator());
initializeSpinnerItemAnimators();
initializeSpinnerScrollAnimators();
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mAdapter.setSwipeEnabled(true)
.getItemTouchHelperCallback()
- .setSwipeFlags(ItemTouchHelper.RIGHT);//Enable swipe
+ .setSwipeFlags(ItemTouchHelper.RIGHT); //Enable swipe
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(false);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
- mAdapter.showLayoutInfo(savedInstanceState == null);
+ // Add 1 Scrollable Header
+ mAdapter.addScrollableHeader(new ScrollableUseCaseItem(
+ getString(R.string.animator_use_case_title),
+ getString(R.string.animator_use_case_description)));
}
@Override
@@ -130,7 +133,7 @@ public void performFabAction() {
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
+ mAdapter.showLayoutInfo(false);
}
@Override
@@ -238,7 +241,7 @@ public enum AnimatorType {
FadeInRight(new FadeInRightAnimator(new OvershootInterpolator(1f))),
Landing(new LandingAnimator(new OvershootInterpolator(1f))),
ScaleIn(new ScaleInAnimator(new OvershootInterpolator(1f))),
- FlipInTopX(new FlipInTopXAnimator(new DecelerateInterpolator(1f))),//Makes use of index inside
+ FlipInTopX(new FlipInTopXAnimator(new DecelerateInterpolator(1f))), //Makes use of index inside
FlipInBottomX(new FlipInBottomXAnimator(new OvershootInterpolator(1f))),
SlideInLeft(new SlideInLeftAnimator(new OvershootInterpolator(1f))),
SlideInRight(new SlideInRightAnimator(new OvershootInterpolator(1f))),
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAsyncFilter.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAsyncFilter.java
index 8638c8a0..ef90cccc 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAsyncFilter.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentAsyncFilter.java
@@ -101,9 +101,13 @@ private void initializeRecyclerView() {
FlipView.resetLayoutAnimationDelay(true, 1000L);
//Experimenting NEW features (v5.0.0)
- mAdapter.setAnimateToLimit(DatabaseConfiguration.animateToLimit)//Size limit = MAX_VALUE will always animate the changes
+ mAdapter.setAnimateChangesWithDiffUtil(DatabaseConfiguration.animateWithDiffUtil)
+ .setAnimateToLimit(DatabaseConfiguration.animateToLimit)//Size limit = MAX_VALUE will always animate the changes
.setNotifyMoveOfFilteredItems(DatabaseConfiguration.notifyMove)//When true, filtering on big list is very slow!
.setNotifyChangeOfUnfilteredItems(DatabaseConfiguration.notifyChange)//We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
+ .setAnimationInitialDelay(100L)
+ .setAnimationOnScrolling(true)
+ .setAnimationOnReverseScrolling(true)
.setOnlyEntryAnimation(true);
if (mRecyclerView == null) {
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
@@ -128,7 +132,7 @@ private void initializeRecyclerView() {
} else {
mFab.setImageResource(R.drawable.ic_settings_white_24dp);
mRecyclerView.removeItemDecoration(mDivider);
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentEndlessScrolling.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentEndlessScrolling.java
index c028d45d..a82393cd 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentEndlessScrolling.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentEndlessScrolling.java
@@ -3,7 +3,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -18,6 +17,7 @@
import eu.davidea.fastscroller.FastScroller;
import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.Payload;
import eu.davidea.flexibleadapter.SelectableAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollGridLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
@@ -25,7 +25,8 @@
import eu.davidea.samples.flexibleadapter.ExampleAdapter;
import eu.davidea.samples.flexibleadapter.MainActivity;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.ProgressItem;
+import eu.davidea.samples.flexibleadapter.animators.FadeInDownAnimator;
+import eu.davidea.samples.flexibleadapter.items.ProgressItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.utils.Utils;
@@ -42,6 +43,7 @@ public class FragmentEndlessScrolling extends AbstractFragment
public static final String TAG = FragmentEndlessScrolling.class.getSimpleName();
private ExampleAdapter mAdapter;
+ private ProgressItem mProgressItem = new ProgressItem();
public static FragmentEndlessScrolling newInstance(int columnCount) {
FragmentEndlessScrolling fragment = new FragmentEndlessScrolling();
@@ -61,120 +63,152 @@ public FragmentEndlessScrolling() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
- DatabaseService.getInstance().createEndlessDatabase(100);//N. of items
+ // Create New Database and Initialize RecyclerView
+ if (savedInstanceState == null) {
+ DatabaseService.getInstance().createEndlessDatabase(0); //N. of items
+ }
initializeRecyclerView(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mAdapter.setAutoScrollOnExpand(true)
- .setHandleDragEnabled(true)
- //.setAnimateToLimit(Integer.MAX_VALUE)//Use the default value
- .setNotifyMoveOfFilteredItems(true)//When true, filtering on big list is very slow, not in this case!
- .setNotifyChangeOfUnfilteredItems(true)//We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
+ //.setAnimateToLimit(Integer.MAX_VALUE) //Use the default value
+ .setNotifyMoveOfFilteredItems(true) //When true, filtering on big list is very slow, not in this case!
+ .setNotifyChangeOfUnfilteredItems(true) //We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
.setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling)
.setAnimationOnReverseScrolling(true);
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
- // a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
- mRecyclerView.setItemAnimator(new DefaultItemAnimator());
+ // NOTE: Use the custom FadeInDownAnimator for ALL notifications for ALL items,
+ // but ScrollableFooterItem implements AnimatedViewHolder with a unique animation: SlideInUp!
+ mRecyclerView.setItemAnimator(new FadeInDownAnimator());
- //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setLongPressDragEnabled(true);//Enable long press to drag items
- mAdapter.setSwipeEnabled(true);//Enable swipe items
- mAdapter.setDisplayHeadersAtStartUp(true);//Show Headers at startUp!
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setLongPressDragEnabled(true) //Enable long press to drag items
+ .setHandleDragEnabled(true) //Enable drag using handle view
+ .setSwipeEnabled(true); //Enable swipe items
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //EndlessScrollListener - OnLoadMore (v5.0.0)
- mAdapter.setEndlessScrollListener(this, new ProgressItem());
- mAdapter.setEndlessScrollThreshold(1);//Default=1
+ // EndlessScrollListener - OnLoadMore (v5.0.0)
+ mAdapter.setEndlessScrollListener(this, mProgressItem)
+ //.setEndlessPageSize(3) //Endless is automatically disabled if newItems < 3
+ .setEndlessTargetCount(15); //Endless is automatically disabled if totalItems >= 15
+ //.setEndlessScrollThreshold(1); //Default=1
- //Add sample HeaderView items on the top (not belongs to the library)
- mAdapter.addUserLearnedSelection(savedInstanceState == null);
+ // Add 1 Scrollable Header and 1 Footer items
mAdapter.showLayoutInfo(savedInstanceState == null);
+ mAdapter.addScrollableFooter();
}
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
+ mAdapter.showLayoutInfo(false);
+ }
+
+ /**
+ * No more data to load.
+ * This method is called if any limit is reached (targetCount or pageSize
+ * must be set) AND if new data is temporary unavailable (ex. no connection or no
+ * new updates remotely). If no new data, a {@link FlexibleAdapter#notifyItemChanged(int, Object)}
+ * with a payload {@link Payload#NO_MORE_LOAD} is triggered on the progressItem.
+ *
+ * @param newItemsSize the last size of the new items loaded
+ * @see FlexibleAdapter#setEndlessTargetCount(int)
+ * @see FlexibleAdapter#setEndlessPageSize(int)
+ * @since 5.0.0-rc1
+ */
+ @Override
+ public void noMoreLoad(int newItemsSize) {
+ Log.d(TAG, "newItemsSize=" + newItemsSize);
+ Log.d(TAG, "Total pages loaded=" + mAdapter.getEndlessCurrentPage());
+ Log.d(TAG, "Total items loaded=" + mAdapter.getMainItemCount());
}
/**
* Loads more data.
+ * Use {@code lastPosition} and {@code currentPage} to know what to load next.
+ * {@code lastPosition} is the count of the main items without Scrollable Headers.
+ *
+ * @param lastPosition the position of the last main item in the adapter
+ * @param currentPage the current page
+ * @since 5.0.0-b6
+ *
5.0.0-rc1 added {@code lastPosition} and {@code currentPage} as parameters
*/
@Override
- public void onLoadMore() {
- //We don't want load more items when searching into the current Collection!
- //Alternatively, for a special filter, if we want load more items when filter is active, the
+ public void onLoadMore(int lastPosition, int currentPage) {
+ // We don't want load more items when searching into the current Collection!
+ // Alternatively, for a special filter, if we want load more items when filter is active, the
// new items that arrive from remote, should be already filtered, before adding them to the Adapter!
if (mAdapter.hasSearchText()) {
mAdapter.onLoadMoreComplete(null);
return;
}
- Log.i(TAG, "onLoadMore invoked!");
- //Simulating asynchronous call
+ // Simulating asynchronous call
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
final List newItems = new ArrayList<>();
- //Simulating success/failure
- int count = new Random().nextInt(5);
- int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_expandable_item);
+ // 1. Simulating success/failure with Random
+ int count = new Random().nextInt(7);
+ int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_simple_item);
for (int i = 1; i <= count; i++) {
- if (i % 2 != 0) {
- newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null));
- } else {
- newItems.add(DatabaseService.newExpandableItem(totalItemsOfType + i, null));
- }
+ newItems.add(DatabaseService.newSimpleItem(totalItemsOfType + i, null));
}
- //Callback the Adapter to notify the change:
- //- New items will be added to the end of the list
- //- When list is null or empty, ProgressItem will be hidden
- mAdapter.onLoadMoreComplete(newItems);
+ // 2. Callback the Adapter to notify the change:
+ // - New items will be added to the end of the main list
+ // - When list is null or empty and limits are reached, Endless scroll will be disabled.
+ // To enable again, you must call setEndlessProgressItem(@Nullable T progressItem).
+ mAdapter.onLoadMoreComplete(newItems, 5000L);
DatabaseService.getInstance().addAll(newItems);
+ // - Retrieve the new page number after adding new items!
+ Log.d(TAG, "EndlessCurrentPage=" + mAdapter.getEndlessCurrentPage());
+ Log.d(TAG, "EndlessPageSize=" + mAdapter.getEndlessPageSize());
+ Log.d(TAG, "EndlessTargetCount=" + mAdapter.getEndlessTargetCount());
- //Expand all Expandable items: Not Expandable items are automatically skipped/ignored!
+ // 3. If you have new Expandable and you want expand them, do as following:
+ // Note: normal items are automatically skipped/ignored because they do not
+ // implement IExpandable interface! So don't care about them.
for (AbstractFlexibleItem item : newItems) {
- //Simple expansion is performed:
- // - Automatic scroll is performed
- //mAdapter.expand(item);
-
- //Initialization is performed:
- // - Expanded status is ignored(WARNING: possible subItem duplication)
+ // Option A. (Best use case) Initialization is performed:
+ // - Expanded status is ignored. WARNING: possible subItems duplication!
// - Automatic scroll is skipped
mAdapter.expand(item, true);
+
+ // Option B. Simple expansion is performed:
+ // - WARNING: Automatic scroll is performed!
+ //mAdapter.expand(item);
}
- //Notify user
- String message = (newItems.size() > 0 ?
- "Simulated: " + newItems.size() + " new items arrived :-)" :
- "Simulated: No more items to load :-(");
- Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
+ // 4. Notify user
+ if (getActivity() != null && newItems.size() > 0) {
+ Toast.makeText(getActivity(),
+ "Simulated: " + newItems.size() + " new items arrived :-)",
+ Toast.LENGTH_SHORT).show();
+ }
}
- }, 2500);
+ }, 4000L);
}
@Override
@@ -212,11 +246,14 @@ protected GridLayoutManager createNewGridLayoutManager() {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- //NOTE: If you use simple integer to identify the ViewType,
- //here, you should use them and not Layout integers
+ // NOTE: If you use simple integers to identify the ViewType,
+ // here, you should use them and not Layout integers
switch (mAdapter.getItemViewType(position)) {
- case R.layout.recycler_layout_item:
- case R.layout.recycler_uls_item:
+ case R.layout.recycler_scrollable_expandable_item:
+ case R.layout.recycler_scrollable_header_item:
+ case R.layout.recycler_scrollable_footer_item:
+ case R.layout.recycler_scrollable_layout_item:
+ case R.layout.recycler_scrollable_uls_item:
case R.layout.progress_item:
return mColumnCount;
default:
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableMultiLevel.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableMultiLevel.java
index 6f7f8967..d9fad955 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableMultiLevel.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableMultiLevel.java
@@ -49,50 +49,49 @@ public FragmentExpandableMultiLevel() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
+ // Create New Database and Initialize RecyclerView
DatabaseService.getInstance().createExpandableMultiLevelDatabase(50);
initializeRecyclerView(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mAdapter.expandItemsAtStartUp()
.setAutoCollapseOnExpand(false)
- .setMinCollapsibleLevel(1)//Auto-collapse only items with level >= 1 (avoid to collapse also sections!)
- .setAutoScrollOnExpand(true)
- .setRemoveOrphanHeaders(false);
+ .setMinCollapsibleLevel(1) //Auto-collapse only items with level >= 1 (avoid to collapse also sections!)
+ .setAutoScrollOnExpand(true);
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setLongPressDragEnabled(true)//Enable long press to drag items
- .setHandleDragEnabled(true)//Enable handle drag
- .setSwipeEnabled(true)//Enable swipe items
- .setDisplayHeadersAtStartUp(true);//Show Headers at startUp!
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setLongPressDragEnabled(true) //Enable long press to drag items
+ .setHandleDragEnabled(true) //Enable handle drag
+ .setSwipeEnabled(true); //Enable swipe items
+ //.setDisplayHeadersAtStartUp(true); //Show Headers at startUp: (not necessary if Headers are also Expandable)
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
+ // Add 2 Scrollable Headers
mAdapter.addUserLearnedSelection(savedInstanceState == null);
mAdapter.showLayoutInfo(savedInstanceState == null);
}
@@ -100,7 +99,7 @@ private void initializeRecyclerView(Bundle savedInstanceState) {
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
+ mAdapter.showLayoutInfo(false);
}
@Override
@@ -109,11 +108,11 @@ protected GridLayoutManager createNewGridLayoutManager() {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- //NOTE: If you use simple integer to identify the ViewType,
- //here, you should use them and not Layout integers
+ // NOTE: If you use simple integers to identify the ViewType,
+ // here, you should use them and not Layout integers
switch (mAdapter.getItemViewType(position)) {
- case R.layout.recycler_layout_item:
- case R.layout.recycler_uls_item:
+ case R.layout.recycler_scrollable_layout_item:
+ case R.layout.recycler_scrollable_uls_item:
case R.layout.recycler_header_item:
case R.layout.recycler_expandable_header_item:
case R.layout.recycler_expandable_item:
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableSections.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableSections.java
index 8b410f67..599d4eb8 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableSections.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentExpandableSections.java
@@ -51,63 +51,62 @@ public FragmentExpandableSections() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
- DatabaseService.getInstance().createExpandableSectionsDatabase(100);//N. of sections
+ // Create New Database and Initialize RecyclerView
+ DatabaseService.getInstance().createExpandableSectionsDatabase(100); //N. of sections
initializeRecyclerView(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mAdapter.expandItemsAtStartUp()
.setAutoCollapseOnExpand(false)
.setAutoScrollOnExpand(true)
- .setAnimateToLimit(Integer.MAX_VALUE)//Size limit = MAX_VALUE will always animate the changes
- .setNotifyMoveOfFilteredItems(false)//When true, filtering on big list is very slow!
- .setNotifyChangeOfUnfilteredItems(true)//We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
- .setRemoveOrphanHeaders(false)
+ .setAnimateToLimit(Integer.MAX_VALUE) //Size limit = MAX_VALUE will always animate the changes
+ .setNotifyMoveOfFilteredItems(false) //When true, filtering on big list is very slow!
+ .setNotifyChangeOfUnfilteredItems(true) //We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
.setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling)
.setAnimationOnReverseScrolling(true);
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Custom divider item decorator
+ // Custom divider item decorator
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),
- R.drawable.divider, 0));//Increase to add gap between sections (Works only with LinearLayout!)
+ R.drawable.divider, 0)); //Increase to add gap between sections (Works only with LinearLayout!)
- //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setLongPressDragEnabled(true)//Enable long press to drag items
- .setHandleDragEnabled(true);//Enable handle drag
- //.setDisplayHeadersAtStartUp(true);//Show Headers at startUp! (not necessary if Headers are also Expandable)
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setLongPressDragEnabled(true) //Enable long press to drag items
+ .setHandleDragEnabled(true); //Enable handle drag
+ //.setDisplayHeadersAtStartUp(true); //Show Headers at startUp: (not necessary if Headers are also Expandable)
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
+ // Add 1 Scrollable Header
mAdapter.showLayoutInfo(savedInstanceState == null);
}
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
+ mAdapter.showLayoutInfo(false);
}
@Override
@@ -116,11 +115,11 @@ protected GridLayoutManager createNewGridLayoutManager() {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- //NOTE: If you use simple integer to identify the ViewType,
- //here, you should use them and not Layout integers
+ // NOTE: If you use simple integers to identify the ViewType,
+ // here, you should use them and not Layout integers
switch (mAdapter.getItemViewType(position)) {
- case R.layout.recycler_layout_item:
- case R.layout.recycler_uls_item:
+ case R.layout.recycler_scrollable_layout_item:
+ case R.layout.recycler_scrollable_uls_item:
case R.layout.recycler_header_item:
case R.layout.recycler_expandable_header_item:
case R.layout.recycler_expandable_item:
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHeadersSections.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHeadersSections.java
index d875de35..6f5e119d 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHeadersSections.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHeadersSections.java
@@ -23,7 +23,8 @@
import eu.davidea.samples.flexibleadapter.R;
import eu.davidea.samples.flexibleadapter.dialogs.BottomSheetDialog;
import eu.davidea.samples.flexibleadapter.dialogs.OnParameterSelectedListener;
-import eu.davidea.samples.flexibleadapter.models.ExpandableHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.ExpandableHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.utils.Utils;
@@ -62,59 +63,62 @@ public FragmentHeadersSections() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
+ // Create New Database and Initialize RecyclerView
DatabaseService.getInstance().createHeadersSectionsDatabase(400, 100);
initializeRecyclerView(savedInstanceState);
- //Restore FAB button and icon
+ // Restore FAB button and icon
initializeFab();
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setRemoveOrphanHeaders(false)
- .setNotifyChangeOfUnfilteredItems(true)//We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setNotifyChangeOfUnfilteredItems(true) //We have highlighted text while filtering, so let's enable this feature to be consistent with the active filter
.setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling);
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
mAdapter.setLongPressDragEnabled(true)
.setHandleDragEnabled(true)
.setSwipeEnabled(true)
.setUnlinkAllItemsOnRemoveHeaders(true)
- //Show Headers at startUp, 1st call, correctly executed, no warning log message!
+ // Show Headers at startUp, 1st call, correctly executed, no warning log message!
.setDisplayHeadersAtStartUp(true)
- .enableStickyHeaders()
- //Simulate developer 2nd call mistake, now it's safe, not executed, no warning log message!
+ .setStickyHeaders(true)
+ // Simulate developer 2nd call mistake, now it's safe, not executed, no warning log message!
.setDisplayHeadersAtStartUp(true)
- //Simulate developer 3rd call mistake, still safe, not executed, warning log message displayed!
+ // Simulate developer 3rd call mistake, still safe, not executed, warning log message displayed!
.showAllHeaders();
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
- //mAdapter.addUserLearnedSelection(savedInstanceState == null);
+ // Add 3 Scrollable Headers and 1 Footer
+ mAdapter.addUserLearnedSelection(savedInstanceState == null);
+ mAdapter.addScrollableHeaderWithDelay(new ScrollableUseCaseItem(
+ getString(R.string.headers_sections_use_case_title),
+ getString(R.string.headers_sections_use_case_description)), 900L, false);
mAdapter.showLayoutInfo(savedInstanceState == null);
+ mAdapter.addScrollableFooter();
}
@Override
@@ -126,7 +130,7 @@ public void performFabAction() {
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
+ mAdapter.showLayoutInfo(false);
}
@Override
@@ -164,7 +168,7 @@ public void onParameterSelected(int itemType, int referencePosition, int childPo
scrollTo = mAdapter.getGlobalPositionOf(referenceHeader);
}
- //With Sticky Headers enabled, this seems necessary to give
+ // With Sticky Headers enabled, this seems necessary to give
// time at the RV to be in correct state before scrolling
final int scrollToFinal = scrollTo;
mRecyclerView.post(new Runnable() {
@@ -186,11 +190,14 @@ protected GridLayoutManager createNewGridLayoutManager() {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- //NOTE: If you use simple integer to identify the ViewType,
- //here, you should use them and not Layout integers
+ // NOTE: If you use simple integers to identify the ViewType,
+ // here, you should use them and not Layout integers
switch (mAdapter.getItemViewType(position)) {
- case R.layout.recycler_layout_item:
- case R.layout.recycler_uls_item:
+ case R.layout.recycler_scrollable_usecase_item:
+ case R.layout.recycler_scrollable_header_item:
+ case R.layout.recycler_scrollable_footer_item:
+ case R.layout.recycler_scrollable_layout_item:
+ case R.layout.recycler_scrollable_uls_item:
case R.layout.recycler_header_item:
case R.layout.recycler_expandable_header_item:
return mColumnCount;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHolderSections.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHolderSections.java
new file mode 100644
index 00000000..c4a63e2f
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentHolderSections.java
@@ -0,0 +1,98 @@
+package eu.davidea.samples.flexibleadapter.fragments;
+
+import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+
+import eu.davidea.fastscroller.FastScroller;
+import eu.davidea.flexibleadapter.SelectableAdapter;
+import eu.davidea.samples.flexibleadapter.ExampleAdapter;
+import eu.davidea.samples.flexibleadapter.MainActivity;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
+import eu.davidea.samples.flexibleadapter.services.DatabaseService;
+import eu.davidea.utils.Utils;
+
+/**
+ * A fragment representing a list of Holder Items.
+ * Activities containing this fragment MUST implement the {@link OnFragmentInteractionListener}
+ * interface.
+ */
+public class FragmentHolderSections extends AbstractFragment {
+
+ public static final String TAG = FragmentHolderSections.class.getSimpleName();
+
+ /**
+ * Custom implementation of FlexibleAdapter
+ */
+ private ExampleAdapter mAdapter;
+
+
+ public static FragmentHolderSections newInstance() {
+ return new FragmentHolderSections();
+ }
+
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public FragmentHolderSections() {
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Create New Database and Initialize RecyclerView
+ DatabaseService.getInstance().createHolderSectionsDatabase(50, 10);
+ initializeRecyclerView(savedInstanceState);
+ }
+
+ @SuppressWarnings({"ConstantConditions", "NullableProblems"})
+ private void initializeRecyclerView(Bundle savedInstanceState) {
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
+
+ mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
+ mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setHasFixedSize(true); //Size of RV will not change
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator());
+
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
+ Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
+ mAdapter.setDisplayHeadersAtStartUp(true)
+ .setStickyHeaders(true)
+ .setOnlyEntryAnimation(true);
+
+ SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
+ swipeRefreshLayout.setEnabled(true);
+ mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
+
+ // Add 1 Scrollable Header
+ mAdapter.addScrollableHeader(new ScrollableUseCaseItem(
+ getString(R.string.model_holders_use_case_title),
+ getString(R.string.model_holders_use_case_description)));
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ Log.v(TAG, "onCreateOptionsMenu called!");
+ inflater.inflate(R.menu.menu_holders, menu);
+ mListener.initSearchView(menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentInstagramHeaders.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentInstagramHeaders.java
index d6c37fa3..ca3076de 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentInstagramHeaders.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentInstagramHeaders.java
@@ -14,12 +14,13 @@
import java.util.List;
import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.Payload;
import eu.davidea.flexibleadapter.SelectableAdapter;
import eu.davidea.flexibleadapter.common.DividerItemDecoration;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flipview.FlipView;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.ProgressItem;
+import eu.davidea.samples.flexibleadapter.items.ProgressItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
/**
@@ -52,75 +53,107 @@ public FragmentInstagramHeaders() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
+ // Create New Database and Initialize RecyclerView
DatabaseService.getInstance().createInstagramHeadersDatabase(15);
- initializeRecyclerView(savedInstanceState);
+ initializeRecyclerView();
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
- private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //true = it makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ private void initializeRecyclerView() {
+ // Initialize Adapter and RecyclerView
+ // true = it makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
mAdapter = new FlexibleAdapter<>(DatabaseService.getInstance().getDatabaseList(), getActivity(), true);
- mAdapter.initializeListeners(getActivity())
- //Experimenting NEW features (v5.0.0)
+ mAdapter.addListener(getActivity())
+ // Experimenting NEW features (v5.0.0)
.setAnimationOnScrolling(true)
.setAnimationOnReverseScrolling(true);
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Custom divider item decorator with 24dpi as empty space between sections
+ // Custom divider item decorator with 24dpi as empty space between sections
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), 0, 24));
- mAdapter.setDisplayHeadersAtStartUp(true)//Show Headers at startUp!
- .enableStickyHeaders()//Make headers sticky
- //Endless scroll with 1 item threshold
+ mAdapter.setDisplayHeadersAtStartUp(true) //Show Headers at startUp!
+ .setStickyHeaders(true) //Make headers sticky
+ // Endless scroll with 1 item threshold
.setEndlessScrollListener(this, new ProgressItem())
- .setEndlessScrollThreshold(1);//Default=1
+ .setEndlessScrollThreshold(1); //Default=1
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
}
+ /**
+ * No more data to load.
+ * This method is called if any limit is reached (targetCount or pageSize
+ * must be set) AND if new data is temporary unavailable (ex. no connection or no
+ * new updates remotely). If no new data, a {@link FlexibleAdapter#notifyItemChanged(int, Object)}
+ * with a payload {@link Payload#NO_MORE_LOAD} is triggered on the progressItem.
+ *
+ * @param newItemsSize the last size of the new items loaded
+ * @see FlexibleAdapter#setEndlessTargetCount(int)
+ * @see FlexibleAdapter#setEndlessPageSize(int)
+ * @since 5.0.0-rc1
+ */
+ @Override
+ public void noMoreLoad(int newItemsSize) {
+ // This method will never be called if No limits are set and loading more will always
+ // produce new items (as this example does)
+ }
+
/**
* Loads more data.
+ * Use {@code lastPosition} and {@code currentPage} to know what to load next.
+ * {@code lastPosition} is the count of the main items without Scrollable Headers.
+ *
+ * @param lastPosition the position of the last main item in the adapter
+ * @param currentPage the current page
+ * @since 5.0.0-b6
+ *
5.0.0-rc1 added {@code lastPosition} and {@code currentPage} as parameters
*/
@Override
- public void onLoadMore() {
+ public void onLoadMore(int lastPosition, int currentPage) {
Log.i(TAG, "onLoadMore invoked!");
- //Simulating asynchronous call
+ // Simulating asynchronous call
new Handler().postDelayed(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
final List newItems = new ArrayList<>(3);
- //Simulating success/failure
+ // Simulating success/failure
int totalItemsOfType = mAdapter.getItemCountOfTypes(R.layout.recycler_instagram_item);
for (int i = 1; i <= 3; i++) {
newItems.add(DatabaseService.newInstagramItem(totalItemsOfType + i));
}
- //Callback the Adapter to notify the change
- //Items will be added to the end of the list
+ // Callback the Adapter to notify the change
+ // Items will be added to the end of the main list
mAdapter.onLoadMoreComplete(newItems);
-
- //Notify user
- String message = "Fetched " + newItems.size() + " new items";
- Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
+ Log.d(TAG, "newItemsSize=" + newItems.size());
+ Log.d(TAG, "EndlessCurrentPage=" + mAdapter.getEndlessCurrentPage());
+ Log.d(TAG, "EndlessPageSize=" + mAdapter.getEndlessPageSize());
+ Log.d(TAG, "EndlessTargetCount=" + mAdapter.getEndlessTargetCount());
+
+ // Notify user
+ if (getActivity() != null && newItems.size() > 0) {
+ Toast.makeText(getActivity(),
+ "Fetched " + newItems.size() + " new items",
+ Toast.LENGTH_SHORT).show();
+ }
}
- }, 2000);
+ }, 3000);
}
@Override
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentOverall.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentOverall.java
index f67bfc12..7d099aa8 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentOverall.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentOverall.java
@@ -16,6 +16,7 @@
import eu.davidea.flexibleadapter.common.SmoothScrollGridLayoutManager;
import eu.davidea.samples.flexibleadapter.OverallAdapter;
import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
/**
@@ -51,43 +52,54 @@ public FragmentOverall() {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Create overall items and Initialize RecyclerView
+ // Create overall items and Initialize RecyclerView
DatabaseService.getInstance().createOverallDatabase(getActivity().getResources());
initializeRecyclerView(savedInstanceState);
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //OverallAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ // Initialize Adapter and RecyclerView
+ // OverallAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+
+ // In this example the Adapter make uses of METHOD B and extends FlexibleAdapter: items
+ // don't implement the AutoMap and don't implement create and binding methods: The Adapter
+ // remains responsible to handling all view types.
mAdapter = new OverallAdapter(getActivity());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setAnimationOnScrolling(true)
- .setAnimationOnReverseScrolling(true)
+
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setOnlyEntryAnimation(true)
.setAnimationInterpolator(new DecelerateInterpolator())
.setAnimationInitialDelay(500L)
- .setAnimationDelay(150L);
+ .setAnimationDelay(70L);
+
+ // Prepare the RecyclerView and attach the Adapter to it
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
- mRecyclerView.setItemViewCacheSize(0);//Setting ViewCache to 0 (default=2) will animate items better while scrolling down+up with LinearLayout
+ mRecyclerView.setItemViewCacheSize(0); //Setting ViewCache to 0 (default=2) will animate items better while scrolling down+up with LinearLayout
mRecyclerView.setLayoutManager(createNewStaggeredGridLayoutManager());
mRecyclerView.setAdapter(mAdapter);
- mRecyclerView.setHasFixedSize(true);//Size of RV will not change
+ mRecyclerView.setHasFixedSize(true); //Size of RV will not change
+
+ // After Adapter is attached to RecyclerView
mAdapter.setLongPressDragEnabled(true);
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
- if (getView() != null) {//Fix NPE when closing app before the execution of Runnable
+ if (getView() != null) { //Fix NPE when closing app before the execution of Runnable
Snackbar.make(getView(), "Long press drag is enabled", Snackbar.LENGTH_SHORT).show();
}
}
- }, 1500L);
+ }, 4000L);
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
+ // Add 2 Scrollable Headers
mAdapter.showLayoutInfo(savedInstanceState == null);
+ mAdapter.addScrollableHeader(new ScrollableUseCaseItem(
+ getString(R.string.overall_use_case_title),
+ getString(R.string.overall_use_case_description)));
}
@Override
@@ -109,10 +121,11 @@ protected GridLayoutManager createNewGridLayoutManager() {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- //NOTE: If you use simple integer to identify the ViewType,
- //here, you should use them and not Layout integers
+ // NOTE: If you use simple integers to identify the ViewType,
+ // here, you should use them and not Layout integers
switch (mAdapter.getItemViewType(position)) {
- case R.layout.recycler_layout_item:
+ case R.layout.recycler_scrollable_usecase_item:
+ case R.layout.recycler_scrollable_layout_item:
return mColumnCount;
default:
return 1;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentSelectionModes.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentSelectionModes.java
index 610ed1b3..99c853cf 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentSelectionModes.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentSelectionModes.java
@@ -5,20 +5,21 @@
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.widget.AdapterView;
+
+import java.util.List;
import eu.davidea.fastscroller.FastScroller;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.SelectableAdapter;
import eu.davidea.flexibleadapter.common.DividerItemDecoration;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flipview.FlipView;
import eu.davidea.samples.flexibleadapter.ExampleAdapter;
import eu.davidea.samples.flexibleadapter.MainActivity;
import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.utils.Utils;
@@ -27,22 +28,10 @@
* Activities containing this fragment MUST implement the {@link OnFragmentInteractionListener}
* interface.
*/
-public class FragmentSelectionModes extends AbstractFragment
- implements FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener {
+public class FragmentSelectionModes extends AbstractFragment {
public static final String TAG = FragmentSelectionModes.class.getSimpleName();
- /**
- * The serialization (saved instance state) Bundle key representing the
- * activated item position. Only used on tablets.
- */
- private static final String STATE_ACTIVATED_POSITION = "activated_position";
-
- /**
- * The current activated item position.
- */
- private int mActivatedPosition = RecyclerView.NO_POSITION;
-
/**
* Custom implementation of FlexibleAdapter
*/
@@ -65,47 +54,40 @@ public static FragmentSelectionModes newInstance(int columnCount) {
public FragmentSelectionModes() {
}
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (savedInstanceState != null) {
- //Previously serialized activated item position
- if (savedInstanceState.containsKey(STATE_ACTIVATED_POSITION))
- setSelection(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
- }
- }
-
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.resetLayoutAnimationDelay(true, 1000L);
- //Create New Database and Initialize RecyclerView
+ // Create New Database and Initialize RecyclerView
DatabaseService.getInstance().createEndlessDatabase(200);
initializeRecyclerView(savedInstanceState);
- //Settings for FlipView
+ // Settings for FlipView
FlipView.stopLayoutAnimation();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
- mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- mAdapter.setMode(SelectableAdapter.MODE_SINGLE);
+ //Get copy of the Database list
+ List items = DatabaseService.getInstance().getDatabaseList();
+
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ mAdapter = new ExampleAdapter(items, getActivity());
+ mAdapter.setNotifyChangeOfUnfilteredItems(true) //This will rebind new item when refreshed
+ .setMode(SelectableAdapter.MODE_SINGLE);
- //Experimenting NEW features (v5.0.0)
+ // Experimenting NEW features (v5.0.0)
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(createNewLinearLayoutManager());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Divider item decorator with DrawOver enabled
+ // Divider item decorator with DrawOver enabled
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), R.drawable.divider)
.withDrawOver(true));
mRecyclerView.postDelayed(new Runnable() {
@@ -115,75 +97,26 @@ public void run() {
}
}, 1500L);
- //Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
- mAdapter.setFastScroller((FastScroller) getActivity().findViewById(R.id.fast_scroller),
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ mAdapter.setFastScroller((FastScroller) getView().findViewById(R.id.fast_scroller),
Utils.getColorAccent(getActivity()), (MainActivity) getActivity());
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_SINGLE);
- //Add sample HeaderView items on the top (not belongs to the library)
+ // Add 2 Scrollable Headers
mAdapter.addUserLearnedSelection(savedInstanceState == null);
- mAdapter.showLayoutInfo(savedInstanceState == null);
+ mAdapter.addScrollableHeaderWithDelay(new ScrollableUseCaseItem(
+ getString(R.string.selection_modes_use_case_title),
+ getString(R.string.selection_modes_use_case_description)), 1100L, true
+ );
}
@Override
public void showNewLayoutInfo(MenuItem item) {
super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
- }
-
- //TODO: Should include setActivatedPosition in the library?
- public void setSelection(final int position) {
- if (mAdapter.getMode() == FlexibleAdapter.MODE_SINGLE) {
- Log.v(TAG, "setSelection called!");
- setActivatedPosition(position);
- mRecyclerView.postDelayed(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.smoothScrollToPosition(position);
- }
- }, 1000L);
- }
- }
-
- private void setActivatedPosition(int position) {
- Log.d(TAG, "ItemList New mActivatedPosition=" + position);
- mActivatedPosition = position;
- }
-
- /**
- * Called when single tap occurs.
- *
- * @param position the adapter position of the item clicked
- * @return true if the click should activate the ItemView, false for no change.
- */
- @Override
- public boolean onItemClick(int position) {
- if (position != mActivatedPosition)
- setActivatedPosition(position);
- return true;
- }
-
- /**
- * Called when long tap occurs.
- *
- * @param position the adapter position of the item clicked
- */
- @Override
- public void onItemLongClick(int position) {
- //TODO: Handling ActionMode
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (mActivatedPosition != AdapterView.INVALID_POSITION) {
- //Serialize and persist the activated item position.
- outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
- Log.d(TAG, STATE_ACTIVATED_POSITION + "=" + mActivatedPosition);
- }
- super.onSaveInstanceState(outState);
+ mAdapter.showLayoutInfo(false);
}
@Override
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentStaggeredLayout.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentStaggeredLayout.java
index 8e56a2cb..526b0468 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentStaggeredLayout.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentStaggeredLayout.java
@@ -12,16 +12,18 @@
import java.util.Random;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.Payload;
import eu.davidea.flexibleadapter.SelectableAdapter;
import eu.davidea.flexibleadapter.common.TopSnappedSmoothScroller;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.flexibleadapter.utils.Utils;
-import eu.davidea.samples.flexibleadapter.ExampleAdapter;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.StaggeredHeaderItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredItemStatus;
+import eu.davidea.samples.flexibleadapter.items.ScrollableUseCaseItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredItemStatus;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
/**
@@ -34,7 +36,7 @@ public class FragmentStaggeredLayout extends AbstractFragment {
public static final String TAG = FragmentStaggeredLayout.class.getSimpleName();
- private ExampleAdapter mAdapter;
+ private FlexibleAdapter mAdapter;
public static FragmentStaggeredLayout newInstance(int columnCount) {
FragmentStaggeredLayout fragment = new FragmentStaggeredLayout();
@@ -55,48 +57,50 @@ public FragmentStaggeredLayout() {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- //Create New Database and Initialize RecyclerView
+ // Create New Database and Initialize RecyclerView
DatabaseService.getInstance().createStaggeredDatabase(getActivity());
initializeRecyclerView(savedInstanceState);
- //Restore FAB button and icon
+ // Restore FAB button and icon
initializeFab();
}
@SuppressWarnings({"ConstantConditions", "NullableProblems"})
private void initializeRecyclerView(Bundle savedInstanceState) {
- //Initialize Adapter and RecyclerView
- //ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
- mAdapter = new ExampleAdapter(DatabaseService.getInstance().getDatabaseList(), getActivity());
- mAdapter.setNotifyMoveOfFilteredItems(true);
+ // Initialize Adapter and RecyclerView
+ // ExampleAdapter makes use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ mAdapter = new FlexibleAdapter<>(DatabaseService.getInstance().getDatabaseList(), getActivity());
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
-
- //Customize the speed of the smooth scroll.
- //NOTE: Every time you change this value you MUST recreate the LayoutManager instance
+ // Customize the speed of the smooth scroll.
+ // NOTE: Every time you change this value you MUST recreate the LayoutManager instance
// and to assign it again to the RecyclerView!
TopSnappedSmoothScroller.MILLISECONDS_PER_INCH = 33f;
mRecyclerView.setLayoutManager(createNewStaggeredGridLayoutManager());
- //This value is restored to 100f (default) right here, because it is used in the constructor
- // by Android. If we don't change it now, others LayoutManager will be impacted too by the
- // above modification!
+ // This value is restored to 100f (default) right here, because it is used in the
+ // constructor by Android. If we don't change it now, others LayoutManager will be
+ // impacted too by the above modification!
TopSnappedSmoothScroller.MILLISECONDS_PER_INCH = 100f;
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true); //Size of RV will not change
- //NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
// a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- //Experimenting NEW features (v5.0.0)
- mAdapter.setDisplayHeadersAtStartUp(true)//Show Headers at startUp!
- .setPermanentDelete(true);
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setDisplayHeadersAtStartUp(true) //Show Headers at startUp!
+ .setNotifyMoveOfFilteredItems(true)
+ .setPermanentDelete(true) //Default=true
+ .setOnlyEntryAnimation(true);
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setEnabled(true);
mListener.onFragmentChange(swipeRefreshLayout, mRecyclerView, SelectableAdapter.MODE_IDLE);
- //Add sample HeaderView items on the top (not belongs to the library)
- mAdapter.showLayoutInfo(savedInstanceState == null);
+ // Add 1 Scrollable Header
+ mAdapter.addScrollableHeader(new ScrollableUseCaseItem(
+ getString(R.string.staggered_use_case_title),
+ getString(R.string.staggered_use_case_description)));
}
@Override
@@ -104,12 +108,6 @@ public int getContextMenuResId() {
return R.menu.menu_staggered_context;
}
- @Override
- public void showNewLayoutInfo(MenuItem item) {
- super.showNewLayoutInfo(item);
- mAdapter.showLayoutInfo(true);
- }
-
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
@@ -127,9 +125,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
} else if (id == R.id.action_delete) {
DatabaseService.getInstance().removeAll();
mAdapter.updateDataSet(null, true);
- //This is necessary if we call updateDataSet() and not removeItems
+ // This is necessary if we call updateDataSet() and not removeItems
DatabaseService.getInstance().resetHeaders();
- //Change fab action (ADD NEW ITEM UNTIL 15)
+ // Change fab action (ADD NEW ITEM UNTIL 15)
FloatingActionButton fab = (FloatingActionButton) getActivity().findViewById(R.id.fab);
fab.setImageResource(R.drawable.fab_add);
}
@@ -143,34 +141,34 @@ public void performFabAction() {
StaggeredHeaderItem headerItem = DatabaseService.getInstance().getHeaderByStatus(status);
int scrollTo;
- //CALCULATE POSITION FOR
- //- Useful in ALL situations of moving/adding an item.
- //- Position is calculated based on the custom Comparator implementation.
- //- Comparator object should sort the Section (in the eventuality the header is hidden)
- // and the Item into the Section (see the Class ItemComparatorByGroup for an
- // example of implementation).
- //- It respects the custom sort, also for a non-displayed section!
- //- When moving/adding, the relative header item will be automatically displayed too
- // if not yet visible.
+ // CALCULATE POSITION FOR
+ // - Useful in ALL situations of moving/adding an item.
+ // - Position is calculated based on the custom Comparator implementation.
+ // - Comparator object should sort the Section (in the eventuality the header is hidden)
+ // and the Item into the Section (see the Class ItemComparatorByGroup for an
+ // example of implementation).
+ // - It respects the custom sort, also for a non-displayed section!
+ // - When moving/adding, the relative header item will be automatically displayed too
+ // if not yet visible.
if (mAdapter.getItemCountOfTypes(R.layout.recycler_staggered_item) >= 15) {
//FAB Action: Move Item
scrollTo = moveItem(status, headerItem);
}
- //ADD ITEM TO SECTION
- //- Useful only to add new items of every type
- //- Comparator object should sort the Section (in the eventuality the header is hidden)
- // and the Item into the Section (see the Class ItemComparatorByGroup for an
- // example of implementation).
- //- The relative header will be automatically displayed too if not yet visible.
- //- if you already know the relative index of the new item, then call the correct
- // method without the Comparator object.
+ // ADD ITEM TO SECTION
+ // - Useful only to add new items of every type
+ // - Comparator object should sort the Section (in the eventuality the header is hidden)
+ // and the Item into the Section (see the Class ItemComparatorByGroup for an
+ // example of implementation).
+ // - The relative header will be automatically displayed too if not yet visible.
+ // - if you already know the relative index of the new item, then call the correct
+ // method without the Comparator object.
else {
- //FAB Action: Add Item
+ // FAB Action: Add Item
scrollTo = addItem(status, headerItem);
}
- //Show to the user the result of the addition/changes
+ // Show to the user the result of the addition/changes
smoothScrollTo(scrollTo, headerItem);
refreshItem(scrollTo);
clearEmptySections();
@@ -181,19 +179,19 @@ private int addItem(StaggeredItemStatus status, StaggeredHeaderItem headerItem)
DatabaseService.getInstance().getMaxStaggeredId(), headerItem);
staggeredItem.setStatus(status);//!!!
- //The section object is known
+ // The section object is known
mAdapter.addItemToSection(staggeredItem, staggeredItem.getHeader(),
new DatabaseService.ItemComparatorByGroup());
- //Add Item to the Database as well for next refresh
+ // Add Item to the Database as well for next refresh
DatabaseService.getInstance().addItem(staggeredItem, new DatabaseService.ItemComparatorById());
- //Change fab action (MOVE ITEM)
+ // Change fab action (MOVE ITEM)
if (mAdapter.getItemCountOfTypes(R.layout.recycler_staggered_item) >= 15) {
FloatingActionButton fab = (FloatingActionButton) getActivity().findViewById(R.id.fab);
fab.setImageResource(R.drawable.ic_sort_white_24dp);
}
- //Retrieve the final position due to a possible hidden header became now visible!
+ // Retrieve the final position due to a possible hidden header became now visible!
int scrollTo = mAdapter.getGlobalPositionOf(staggeredItem);
Log.d(TAG, "Creating New Item " + staggeredItem + " at position " + scrollTo);
return scrollTo;
@@ -202,22 +200,22 @@ private int addItem(StaggeredItemStatus status, StaggeredHeaderItem headerItem)
private int moveItem(StaggeredItemStatus status, StaggeredHeaderItem headerItem) {
StaggeredItem staggeredItem = DatabaseService.getInstance().getRandomStaggeredItem();
if (!staggeredItem.getHeader().equals(headerItem)) {
- //Before calculate the position, change header/section
+ // Before calculate the position, change header/section
staggeredItem.setStatus(status);//!!!
staggeredItem.setHeader(headerItem);
int toPosition = mAdapter.calculatePositionFor(staggeredItem, new DatabaseService.ItemComparatorByGroup());
- //Move item to just calculated position under the correct section
+ // Move item to just calculated position under the correct section
mAdapter.moveItem(mAdapter.getGlobalPositionOf(staggeredItem), toPosition, Payload.MOVE);
}
- //Retrieve the final position due to a possible hidden header became now visible!
+ // Retrieve the final position due to a possible hidden header became now visible!
int scrollTo = mAdapter.getGlobalPositionOf(staggeredItem);
Log.d(TAG, "Moving Item to position" + scrollTo);
return scrollTo;
}
private void smoothScrollTo(final int scrollTo, final StaggeredHeaderItem headerItem) {
- //Smooth scrolling should be delayed because the just added item could not be yet
+ // Smooth scrolling should be delayed because the just added item could not be yet
// animated/rendered by the LayoutManager
mRecyclerView.postDelayed(new Runnable() {
@Override
@@ -237,7 +235,7 @@ public void run() {
}
private void refreshItem(final int position) {
- //We notify the item that it is changed, bind it again (change color)
+ // We notify the item that it is changed, bind it again (change color)
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
@@ -247,7 +245,7 @@ public void run() {
}
private void clearEmptySections() {
- //We remove the header item that is without items (empty sections)
+ // We remove the header item that is without items (empty sections)
for (final IHeader header : mAdapter.getHeaderItems()) {
Log.d(TAG, "Header=" + header.toString() + " Items=" + mAdapter.getSectionItems(header).size());
if (mAdapter.getSectionItems(header).size() == 0) {
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentViewPager.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentViewPager.java
new file mode 100644
index 00000000..b81f47c0
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/fragments/FragmentViewPager.java
@@ -0,0 +1,161 @@
+package eu.davidea.samples.flexibleadapter.fragments;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.davidea.fastscroller.FastScroller;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
+import eu.davidea.flexibleadapter.items.IFlexible;
+import eu.davidea.flipview.FlipView;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.items.HeaderItem;
+import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
+import eu.davidea.samples.flexibleadapter.services.DatabaseService;
+import eu.davidea.utils.Utils;
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+public class FragmentViewPager extends Fragment {
+ /**
+ * The fragment argument representing the section number for this
+ * fragment.
+ */
+ private static final String ARG_SECTION_NUMBER = "section_number";
+ private static final String TAG = FragmentViewPager.class.getSimpleName();
+
+ private int mSection;
+ private FlexibleAdapter mAdapter;
+
+ public FragmentViewPager() {
+ }
+
+ /**
+ * Returns a new instance of this fragment for the given section
+ * number.
+ */
+ public static FragmentViewPager newInstance(int sectionNumber) {
+ FragmentViewPager fragment = new FragmentViewPager();
+ Bundle args = new Bundle();
+ args.putInt(ARG_SECTION_NUMBER, sectionNumber);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null) {
+ mSection = getArguments().getInt(ARG_SECTION_NUMBER);
+ Log.d(TAG, "Creating new Fragment for Section " + mSection);
+ }
+
+ // Contribution for specific action buttons in the Toolbar
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_view_pager, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Settings for FlipView
+ FlipView.resetLayoutAnimationDelay(true, 1000L);
+
+ // Initialize RecyclerView
+ initializeRecyclerView();
+
+ // Settings for FlipView
+ FlipView.stopLayoutAnimation();
+ }
+
+ private void initializeRecyclerView() {
+ // Initialize Adapter and RecyclerView
+ // Use of stableIds, I strongly suggest to implement 'item.hashCode()'
+ mAdapter = new FlexibleAdapter<>(createList(50, 5), getActivity(), true);
+ // Experimenting NEW features (v5.0.0)
+ mAdapter.setAnimationOnScrolling(DatabaseConfiguration.animateOnScrolling);
+
+ RecyclerView mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
+ mRecyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(getActivity()));
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setHasFixedSize(true); //Size of RV will not change
+ // NOTE: Use default item animator 'canReuseUpdatedViewHolder()' will return true if
+ // a Payload is provided. FlexibleAdapter is actually sending Payloads onItemChange.
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator());
+
+ // Add FastScroll to the RecyclerView, after the Adapter has been attached the RecyclerView!!!
+ FastScroller fastScroller = (FastScroller) getView().findViewById(R.id.fast_scroller);
+ mAdapter.setFastScroller(fastScroller, Utils.getColorAccent(getActivity()));
+ mAdapter.toggleFastScroller();
+
+ // Sticky Headers
+ mAdapter.setDisplayHeadersAtStartUp(true)
+ .setStickyHeaders(true);
+ }
+
+ private List createList(int size, int headers) {
+ HeaderItem header = null;
+ List items = new ArrayList<>();
+ int lastHeaderId = 0;
+ for (int i = 0; i < size; i++) {
+ header = i % Math.round(size / headers) == 0 ?
+ DatabaseService.newHeader(++lastHeaderId) : header;
+ items.add(DatabaseService.newSimpleItem(i + 1, header));
+ }
+ return items;
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ // Headers are shown?
+ MenuItem headersMenuItem = menu.findItem(R.id.action_show_hide_headers);
+ if (headersMenuItem != null) {
+ headersMenuItem.setTitle(mAdapter.areHeadersShown() ? R.string.hide_headers : R.string.show_headers);
+ }
+ // Sticky Header item?
+ MenuItem stickyItem = menu.findItem(R.id.action_sticky_headers);
+ if (stickyItem != null) {
+ stickyItem.setEnabled(mAdapter.areHeadersShown());
+ stickyItem.setChecked(mAdapter.areHeadersSticky());
+ }
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.action_sticky_headers) {
+ item.setChecked(!mAdapter.areHeadersSticky());
+ mAdapter.setStickyHeaders(!mAdapter.areHeadersSticky());
+ } else if (id == R.id.action_show_hide_headers) {
+ if (mAdapter.areHeadersShown()) {
+ mAdapter.hideAllHeaders();
+ item.setTitle(R.string.show_headers);
+ } else {
+ mAdapter.showAllHeaders();
+ item.setTitle(R.string.hide_headers);
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java
new file mode 100644
index 00000000..61eee3ab
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/HeaderHolder.java
@@ -0,0 +1,100 @@
+package eu.davidea.samples.flexibleadapter.holders;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.items.AbstractHeaderItem;
+import eu.davidea.flexibleadapter.items.IFilterable;
+import eu.davidea.flexibleadapter.items.IHolder;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.models.HeaderModel;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * The holder item is just a wrapper for the Model item.
+ *
+ * @author Davide Steduto
+ * @since 19/10/2016
+ */
+public class HeaderHolder extends AbstractHeaderItem
+ implements IFilterable, IHolder {
+
+ private HeaderModel model;
+
+ public HeaderHolder(HeaderModel model) {
+ this.model = model;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof HeaderHolder) {
+ HeaderHolder inItem = (HeaderHolder) o;
+ return model.equals(inItem.getModel());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return model.hashCode();
+ }
+
+ /**
+ * @return the model object
+ */
+ @Override
+ public HeaderModel getModel() {
+ return model;
+ }
+
+ /**
+ * Filter is applied to the model fields.
+ */
+ @Override
+ public boolean filter(String constraint) {
+ return model.getTitle() != null && model.getTitle().equals(constraint);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_holder_header;
+ }
+
+ @Override
+ public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new HeaderViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(final FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) {
+ holder.mTitle.setText(model.getTitle());
+ List sectionableList = adapter.getSectionItems(this);
+ String subTitle = (sectionableList.isEmpty() ? "Empty section" :
+ sectionableList.size() + " section items");
+ holder.mSubtitle.setText(subTitle);
+ }
+
+ static class HeaderViewHolder extends FlexibleViewHolder {
+
+ @BindView(R.id.title)
+ public TextView mTitle;
+ @BindView(R.id.subtitle)
+ public TextView mSubtitle;
+
+ /**
+ * Default constructor.
+ */
+ public HeaderViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter, true);//true only for header items when will be sticky
+ ButterKnife.bind(this, view);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java
new file mode 100644
index 00000000..8af3ff68
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/holders/ItemHolder.java
@@ -0,0 +1,109 @@
+package eu.davidea.samples.flexibleadapter.holders;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.items.AbstractSectionableItem;
+import eu.davidea.flexibleadapter.items.IFilterable;
+import eu.davidea.flexibleadapter.items.IHolder;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.models.ItemModel;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * The holder item is just a wrapper for the Model item.
+ *
+ * Holder item can be used to display the same modelData in multiple RecyclerViews managed by
+ * different Adapters, you can implement a derived IFlexible item to HOLD your data model object!
+ *
+ * In this way you can separate the memory zones of the flags (enabled, expanded, hidden,
+ * selectable) used by a specific Adapter, to be independent by another Adapter. For instance,
+ * an item can be Shown and Expanded in a RV, while in the other RV can be Hidden or Not Expanded!
+ *
+ * @author Davide Steduto
+ * @since 19/10/2016
+ */
+public class ItemHolder extends AbstractSectionableItem
+ implements IFilterable, IHolder {
+
+ private ItemModel model;
+
+ /**
+ * The header item must in its bounds, it must implement IHeader, therefore: HeaderHolder!
+ */
+ public ItemHolder(ItemModel model, HeaderHolder header) {
+ super(header);
+ this.model = model;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ItemHolder) {
+ ItemHolder inItem = (ItemHolder) o;
+ return model.equals(inItem.getModel());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return model.hashCode();
+ }
+
+ /**
+ * @return the model object
+ */
+ @Override
+ public ItemModel getModel() {
+ return model;
+ }
+
+ /**
+ * Filter is applied to the model fields.
+ */
+ @Override
+ public boolean filter(String constraint) {
+ return model.getTitle() != null && model.getTitle().toLowerCase().trim().contains(constraint) ||
+ model.getSubtitle() != null && model.getSubtitle().toLowerCase().trim().contains(constraint);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_holder_item;
+ }
+
+ @Override
+ public ItemViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ItemViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(final FlexibleAdapter adapter, ItemViewHolder holder, int position, List payloads) {
+ holder.mTitle.setText(model.getTitle());
+ holder.mSubtitle.setText(model.getSubtitle());
+ }
+
+ static class ItemViewHolder extends FlexibleViewHolder {
+
+ @BindView(R.id.title)
+ public TextView mTitle;
+ @BindView(R.id.subtitle)
+ public TextView mSubtitle;
+
+ /**
+ * Default constructor.
+ */
+ public ItemViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ ButterKnife.bind(this, view);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModelItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java
similarity index 65%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModelItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java
index 2478ad29..0d65a1f0 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModelItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AbstractItem.java
@@ -1,6 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
-
-import java.io.Serializable;
+package eu.davidea.samples.flexibleadapter.items;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.viewholders.FlexibleViewHolder;
@@ -9,26 +7,25 @@
* This class will benefit of the already implemented methods (getter and setters) in
* {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem}.
*
- * It is used as Base item for all example models.
+ * It is used as base item for all example models.
*/
-public abstract class AbstractModelItem
- extends AbstractFlexibleItem
- implements Serializable {
-
- private static final long serialVersionUID = -6882745111884490060L;
+public abstract class AbstractItem
+ extends AbstractFlexibleItem {
- private String id;
- private String title;
- private String subtitle;
+ protected String id;
+ protected String title;
+ protected String subtitle = "";
+ /* number of times this item has been refreshed */
+ protected int updates;
- public AbstractModelItem(String id) {
+ public AbstractItem(String id) {
this.id = id;
}
@Override
public boolean equals(Object inObject) {
- if (inObject instanceof AbstractModelItem) {
- AbstractModelItem inItem = (AbstractModelItem) inObject;
+ if (inObject instanceof AbstractItem) {
+ AbstractItem inItem = (AbstractItem) inObject;
return this.id.equals(inItem.id);
}
return false;
@@ -67,6 +64,14 @@ public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
+ public int getUpdates() {
+ return updates;
+ }
+
+ public void increaseUpdates() {
+ this.updates++;
+ }
+
@Override
public String toString() {
return "id=" + id +
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorExpandableItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorExpandableItem.java
similarity index 96%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorExpandableItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorExpandableItem.java
index 7a33265e..5c0ebe80 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorExpandableItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorExpandableItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.support.annotation.NonNull;
@@ -15,7 +15,7 @@
import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.AnimatorExpandableItem.AnimatorExpandableViewHolder;
+import eu.davidea.samples.flexibleadapter.items.AnimatorExpandableItem.AnimatorExpandableViewHolder;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
import eu.davidea.viewholders.ExpandableViewHolder;
@@ -89,7 +89,7 @@ public AnimatorExpandableViewHolder createViewHolder(FlexibleAdapter adapter, La
@Override
public void bindViewHolder(FlexibleAdapter adapter, AnimatorExpandableViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "AnimatorExpandableItem Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "AnimatorExpandableItem Payload " + payloads);
} else {
holder.mTitle.setText(getTitle());
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorSubItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java
similarity index 97%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorSubItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java
index 24983ac5..4024ebd7 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AnimatorSubItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/AnimatorSubItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.support.annotation.NonNull;
@@ -92,7 +92,7 @@ static final class ChildViewHolder extends FlexibleViewHolder implements Animate
public TextView mTitle;
- public ChildViewHolder(View view, FlexibleAdapter adapter) {
+ ChildViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
this.mTitle = (TextView) view.findViewById(R.id.title);
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ConfigurationItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ConfigurationItem.java
similarity index 99%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ConfigurationItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ConfigurationItem.java
index 0b3b5383..2fbc9fda 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ConfigurationItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ConfigurationItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.support.annotation.IntDef;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java
similarity index 56%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableHeaderItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java
index 434b424e..1683e4ba 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableHeaderItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableHeaderItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
@@ -12,10 +12,11 @@
import java.util.List;
import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.Payload;
import eu.davidea.flexibleadapter.items.IExpandable;
import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.ExpandableHeaderItem.ExpandableHeaderViewHolder;
+import eu.davidea.samples.flexibleadapter.items.ExpandableHeaderItem.ExpandableHeaderViewHolder;
import eu.davidea.viewholders.ExpandableViewHolder;
/**
@@ -24,12 +25,10 @@
* It's important to note that, the ViewHolder must be specified in all <diamond> signature.
*/
public class ExpandableHeaderItem
- extends AbstractModelItem
+ extends AbstractItem
implements IExpandable,
IHeader {
- private static final long serialVersionUID = -1882711111814491060L;
-
/* Flags for FlexibleAdapter */
private boolean mExpanded = false;
@@ -109,7 +108,7 @@ public ExpandableHeaderViewHolder createViewHolder(FlexibleAdapter adapter, Layo
@Override
public void bindViewHolder(FlexibleAdapter adapter, ExpandableHeaderViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
} else {
holder.mTitle.setText(getTitle());
}
@@ -125,11 +124,11 @@ public void bindViewHolder(FlexibleAdapter adapter, ExpandableHeaderViewHolder h
*/
static class ExpandableHeaderViewHolder extends ExpandableViewHolder {
- public TextView mTitle;
- public TextView mSubtitle;
- public ImageView mHandleView;
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mHandleView;
- public ExpandableHeaderViewHolder(View view, FlexibleAdapter adapter) {
+ ExpandableHeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter, true);//True for sticky
mTitle = (TextView) view.findViewById(R.id.title);
mSubtitle = (TextView) view.findViewById(R.id.subtitle);
@@ -147,23 +146,93 @@ public ExpandableHeaderViewHolder(View view, FlexibleAdapter adapter) {
}
}
+ /**
+ * Allows to expand or collapse child views of this itemView when {@link View.OnClickListener}
+ * event occurs on the entire view.
+ * This method returns always true; Extend with "return false" to Not expand or collapse
+ * this ItemView onClick events.
+ *
+ * @return always true, if not overridden
+ * @since 5.0.0-b1
+ */
@Override
protected boolean isViewExpandableOnClick() {
- return true;//true by default
+ return true;//default=true
+ }
+
+ /**
+ * Allows to collapse child views of this ItemView when {@link View.OnLongClickListener}
+ * event occurs on the entire view.
+ * This method returns always true; Extend with "return false" to Not collapse this
+ * ItemView onLongClick events.
+ *
+ * @return always true, if not overridden
+ * @since 5.0.0-b1
+ */
+ protected boolean isViewCollapsibleOnLongClick() {
+ return true;//default=true
+ }
+
+ /**
+ * Allows to notify change and rebound this itemView on expanding and collapsing events,
+ * in order to update the content (so, user can decide to display the current expanding status).
+ * This method returns always false; Override with {@code "return true"} to trigger the
+ * notification.
+ *
+ * @return true to rebound the content of this itemView on expanding and collapsing events,
+ * false to ignore the events
+ * @see #expandView(int)
+ * @see #collapseView(int)
+ * @since 5.0.0-rc1
+ */
+ @Override
+ protected boolean shouldNotifyParentOnClick() {
+ return true;//default=false
+ }
+
+ /**
+ * Expands or Collapses based on the current state.
+ *
+ * @see #shouldNotifyParentOnClick()
+ * @see #expandView(int)
+ * @see #collapseView(int)
+ * @since 5.0.0-b1
+ */
+ @Override
+ protected void toggleExpansion() {
+ super.toggleExpansion(); //If overridden, you must call the super method
}
+ /**
+ * Triggers expansion of this itemView.
+ * If {@link #shouldNotifyParentOnClick()} returns {@code true}, this view is rebound
+ * with payload {@link Payload#EXPANDED}.
+ *
+ * @see #shouldNotifyParentOnClick()
+ * @since 5.0.0-b1
+ */
@Override
protected void expandView(int position) {
- super.expandView(position);
- //Let's notify the item has been expanded
- if (mAdapter.isExpanded(position)) mAdapter.notifyItemChanged(position, true);
+ super.expandView(position); //If overridden, you must call the super method
+ // Let's notify the item has been expanded. Note: from 5.0.0-rc1 the next line becomes
+ // obsolete, override the new method shouldNotifyParentOnClick() as showcased here
+ //if (mAdapter.isExpanded(position)) mAdapter.notifyItemChanged(position, true);
}
+ /**
+ * Triggers collapse of this itemView.
+ * If {@link #shouldNotifyParentOnClick()} returns {@code true}, this view is rebound
+ * with payload {@link Payload#COLLAPSED}.
+ *
+ * @see #shouldNotifyParentOnClick()
+ * @since 5.0.0-b1
+ */
@Override
protected void collapseView(int position) {
- super.collapseView(position);
- //Let's notify the item has been collapsed
- if (!mAdapter.isExpanded(position)) mAdapter.notifyItemChanged(position, true);
+ super.collapseView(position); //If overridden, you must call the super method
+ // Let's notify the item has been collapsed. Note: from 5.0.0-rc1 the next line becomes
+ // obsolete, override the new method shouldNotifyParentOnClick() as showcased here
+ //if (!mAdapter.isExpanded(position)) mAdapter.notifyItemChanged(position, true);
}
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableItem.java
new file mode 100644
index 00000000..e45ec988
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableItem.java
@@ -0,0 +1,299 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.flexibleadapter.items.IExpandable;
+import eu.davidea.flexibleadapter.items.IFilterable;
+import eu.davidea.flexibleadapter.items.ISectionable;
+import eu.davidea.flexibleadapter.utils.DrawableUtils;
+import eu.davidea.flexibleadapter.utils.Utils;
+import eu.davidea.flipview.FlipView;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.viewholders.ExpandableViewHolder;
+
+/**
+ * If you don't have fields in common better to extend directly from
+ * {@link eu.davidea.flexibleadapter.items.AbstractExpandableItem} to benefit of the already
+ * implemented methods around subItems list.
+ */
+public class ExpandableItem extends AbstractItem
+ implements ISectionable, IFilterable,
+ IExpandable {
+
+ /* The header of this item */
+ HeaderItem header;
+
+ /* subItems list */
+ private List mSubItems;
+
+ /* Flags for FlexibleAdapter */
+ private boolean mExpanded = false;
+
+ public ExpandableItem(String id, HeaderItem header) {
+ super(id);
+ this.header = header;
+ setDraggable(true);
+ setSwipeable(true);
+ }
+
+ @Override
+ public HeaderItem getHeader() {
+ return header;
+ }
+
+ @Override
+ public void setHeader(HeaderItem header) {
+ this.header = header;
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return mExpanded;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ mExpanded = expanded;
+ }
+
+ @Override
+ public int getExpansionLevel() {
+ return 0;
+ }
+
+ @Override
+ public List getSubItems() {
+ return mSubItems;
+ }
+
+ public final boolean hasSubItems() {
+ return mSubItems!= null && mSubItems.size() > 0;
+ }
+
+ public boolean removeSubItem(SubItem item) {
+ return item != null && mSubItems.remove(item);
+ }
+
+ public boolean removeSubItem(int position) {
+ if (mSubItems != null && position >= 0 && position < mSubItems.size()) {
+ mSubItems.remove(position);
+ return true;
+ }
+ return false;
+ }
+
+ public void addSubItem(SubItem subItem) {
+ if (mSubItems == null)
+ mSubItems = new ArrayList<>();
+ mSubItems.add(subItem);
+ }
+
+ public void addSubItem(int position, SubItem subItem) {
+ if (mSubItems != null && position >= 0 && position < mSubItems.size()) {
+ mSubItems.add(position, subItem);
+ } else
+ addSubItem(subItem);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_expandable_item;
+ }
+
+ @Override
+ public ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ParentViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holder, int position, List payloads) {
+ Context context = holder.itemView.getContext();
+
+ // Subtitle
+ setSubtitle(adapter.getCurrentChildren(this).size() + " subItems"
+ + (getHeader() != null ? " - " + getHeader().getId() : "")
+ + (getUpdates() > 0 ? " - u" + getUpdates() : ""));
+
+ // Background, when bound the first time
+ if (payloads.size() == 0) {
+ Drawable drawable = DrawableUtils.getSelectableBackgroundCompat(
+ Color.WHITE, Color.parseColor("#dddddd"), // Same color of divider
+ DrawableUtils.getColorControlHighlight(context));
+ DrawableUtils.setBackgroundCompat(holder.itemView, drawable);
+ DrawableUtils.setBackgroundCompat(holder.frontView, drawable);
+ }
+
+ if (payloads.size() > 0) {
+ Log.d(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads);
+ if (adapter.hasSearchText()) {
+ Utils.highlightText(holder.mSubtitle, getSubtitle(), adapter.getSearchText());
+ } else {
+ holder.mSubtitle.setText(getSubtitle());
+ }
+ // We stop the process here, we only want to update the subtitle
+
+ } else {
+ // DemoApp: INNER ANIMATION EXAMPLE! ImageView - Handle Flip Animation on
+ // Select ALL and Deselect ALL
+ if (adapter.isSelectAll() || adapter.isLastItemInActionMode()) {
+ // Consume the Animation
+ holder.mFlipView.flip(adapter.isSelected(position), 200L);
+ } else {
+ // Display the current flip status
+ holder.mFlipView.flipSilently(adapter.isSelected(position));
+ }
+
+ // In case of searchText matches with Title or with a field this will be highlighted
+ if (adapter.hasSearchText()) {
+ Utils.highlightText(holder.mTitle, getTitle(), adapter.getSearchText());
+ Utils.highlightText(holder.mSubtitle, getSubtitle(), adapter.getSearchText());
+ } else {
+ holder.mTitle.setText(getTitle());
+ holder.mSubtitle.setText(getSubtitle());
+ }
+ }
+ }
+
+ @Override
+ public boolean filter(String constraint) {
+ return getTitle() != null && getTitle().toLowerCase().trim().contains(constraint) ||
+ getSubtitle() != null && getSubtitle().toLowerCase().trim().contains(constraint);
+ }
+
+ /**
+ * This ViewHolder is expandable and collapsible.
+ */
+ static final class ParentViewHolder extends ExpandableViewHolder {
+
+ FlipView mFlipView;
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mHandleView;
+ Context mContext;
+ View frontView;
+ View rearLeftView;
+ View rearRightView;
+
+ public boolean swiped = false;
+
+ ParentViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ this.mContext = view.getContext();
+ this.mTitle = (TextView) view.findViewById(R.id.title);
+ this.mSubtitle = (TextView) view.findViewById(R.id.subtitle);
+ this.mFlipView = (FlipView) view.findViewById(R.id.image);
+ this.mFlipView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mAdapter.mItemLongClickListener != null) {
+ mAdapter.mItemLongClickListener.onItemLongClick(getAdapterPosition());
+ Toast.makeText(mContext, "ImageClick on " + mTitle.getText() + " position " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
+ toggleActivation();
+ }
+ }
+ });
+ this.mHandleView = (ImageView) view.findViewById(R.id.row_handle);
+ setDragHandleView(mHandleView);
+
+ this.frontView = view.findViewById(R.id.front_view);
+ this.rearLeftView = view.findViewById(R.id.rear_left_view);
+ this.rearRightView = view.findViewById(R.id.rear_right_view);
+ }
+
+ @Override
+ protected void setDragHandleView(@NonNull View view) {
+ if (mAdapter.isHandleDragEnabled()) {
+ view.setVisibility(View.VISIBLE);
+ super.setDragHandleView(view);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void toggleActivation() {
+ super.toggleActivation();
+ // Here we use a custom Animation inside the ItemView
+ mFlipView.flip(mAdapter.isSelected(getAdapterPosition()));
+ }
+
+ @Override
+ public float getActivationElevation() {
+ return eu.davidea.utils.Utils.dpToPx(itemView.getContext(), 4f);
+ }
+
+ @Override
+ protected boolean shouldActivateViewWhileSwiping() {
+ return false;//default=false
+ }
+
+ @Override
+ protected boolean shouldAddSelectionInActionMode() {
+ return false;//default=false
+ }
+
+ @Override
+ public View getFrontView() {
+ return frontView;
+ }
+
+ @Override
+ public View getRearLeftView() {
+ return rearLeftView;
+ }
+
+ @Override
+ public View getRearRightView() {
+ return rearRightView;
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ if (mAdapter.getRecyclerView().getLayoutManager() instanceof GridLayoutManager ||
+ mAdapter.getRecyclerView().getLayoutManager() instanceof StaggeredGridLayoutManager) {
+ if (position % 2 != 0)
+ AnimatorHelper.slideInFromRightAnimator(animators, itemView, mAdapter.getRecyclerView(), 0.5f);
+ else
+ AnimatorHelper.slideInFromLeftAnimator(animators, itemView, mAdapter.getRecyclerView(), 0.5f);
+ } else {
+ // Linear layout
+ if (mAdapter.isSelected(position))
+ AnimatorHelper.slideInFromRightAnimator(animators, itemView, mAdapter.getRecyclerView(), 0.5f);
+ else
+ AnimatorHelper.slideInFromLeftAnimator(animators, itemView, mAdapter.getRecyclerView(), 0.5f);
+ }
+ }
+
+ @Override
+ public void onItemReleased(int position) {
+ swiped = (mActionState == ItemTouchHelper.ACTION_STATE_SWIPE);
+ super.onItemReleased(position);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ExpandableItem[" + super.toString() + "//SubItems" + mSubItems + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel0Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java
similarity index 93%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel0Item.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java
index 712a28c0..41e6612b 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel0Item.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel0Item.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
@@ -15,7 +15,7 @@
import eu.davidea.flexibleadapter.items.IExpandable;
import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.ExpandableLevel0Item.L0ViewHolder;
+import eu.davidea.samples.flexibleadapter.items.ExpandableLevel0Item.L0ViewHolder;
import eu.davidea.samples.flexibleadapter.services.DatabaseService;
import eu.davidea.samples.flexibleadapter.services.DatabaseType;
import eu.davidea.viewholders.ExpandableViewHolder;
@@ -26,11 +26,9 @@
* It's important to note that, the ViewHolder must be specified in all <diamond> signature.
*/
public class ExpandableLevel0Item
- extends AbstractModelItem
+ extends AbstractItem
implements IExpandable, IHeader {
- private static final long serialVersionUID = -1882711111814491060L;
-
/* Flags for FlexibleAdapter */
private boolean mExpanded = false;
@@ -109,7 +107,7 @@ public L0ViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inf
@Override
public void bindViewHolder(FlexibleAdapter adapter, L0ViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
} else {
holder.mTitle.setText(getTitle());
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel1Item.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java
similarity index 64%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel1Item.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java
index b17830a9..30beaf42 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableLevel1Item.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ExpandableLevel1Item.java
@@ -1,5 +1,8 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -7,6 +10,7 @@
import java.util.ArrayList;
import java.util.List;
+import eu.davidea.flexibleadapter.utils.DrawableUtils;
import eu.davidea.samples.flexibleadapter.R;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.IExpandable;
@@ -17,10 +21,8 @@
* It's important to note that, the ViewHolder must be specified in all <diamond> signature.
*/
public class ExpandableLevel1Item
- extends AbstractModelItem
- implements IExpandable {
-
- private static final long serialVersionUID = -1882711111814491060L;
+ extends AbstractItem
+ implements IExpandable {
/* Flags for FlexibleAdapter */
private boolean mExpanded = false;
@@ -73,7 +75,7 @@ public boolean removeSubItem(int position) {
public void addSubItem(SubItem subItem) {
if (mSubItems == null)
- mSubItems = new ArrayList();
+ mSubItems = new ArrayList<>();
mSubItems.add(subItem);
}
@@ -90,33 +92,36 @@ public int getLayoutRes() {
}
@Override
- public SimpleItem.ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new SimpleItem.ParentViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ public ExpandableItem.ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ExpandableItem.ParentViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
}
@Override
- public void bindViewHolder(final FlexibleAdapter adapter, SimpleItem.ParentViewHolder holder, int position, List payloads) {
- if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
- } else {
- holder.mTitle.setText(getTitle());
- }
+ public void bindViewHolder(final FlexibleAdapter adapter, ExpandableItem.ParentViewHolder holder, int position, List payloads) {
+ Context context = holder.itemView.getContext();
+
setSubtitle(adapter.getCurrentChildren(this).size() + " subItems");
holder.mSubtitle.setText(getSubtitle());
- //ANIMATION EXAMPLE!! ImageView - Handle Flip Animation on Select ALL and Deselect ALL
+ // Background, when bound the first time
+ if (payloads.size() == 0) {
+ holder.mTitle.setText(getTitle());
+
+ Drawable drawable = DrawableUtils.getSelectableBackgroundCompat(
+ Color.WHITE, Color.parseColor("#dddddd"), // Same color of divider
+ DrawableUtils.getColorControlHighlight(context));
+ DrawableUtils.setBackgroundCompat(holder.itemView, drawable);
+ DrawableUtils.setBackgroundCompat(holder.frontView, drawable);
+ } else {
+ Log.d(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
+ }
+
+ // ANIMATION EXAMPLE!! ImageView - Handle Flip Animation on Select ALL and Deselect ALL
if (adapter.isSelectAll() || adapter.isLastItemInActionMode()) {
- //Reset the flags with delay
- holder.itemView.postDelayed(new Runnable() {
- @Override
- public void run() {
- adapter.resetActionModeFlags();
- }
- }, 200L);
- //Consume the Animation
+ // Consume the Animation
holder.mFlipView.flip(adapter.isSelected(position), 200L);
} else {
- //Display the current flip status
+ // Display the current flip status
holder.mFlipView.flipSilently(adapter.isSelected(position));
}
}
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java
similarity index 87%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java
index 27ef51c6..31484fdd 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/HeaderItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
@@ -18,14 +18,12 @@
import eu.davidea.viewholders.FlexibleViewHolder;
/**
- * This is a simple item with custom layout for headers.
- * A Section should not contain others Sections!
- * Headers are not Sectionable!
+ * This is a header item with custom layout for section headers.
+ * Note: THIS ITEM IS NOT A SCROLLABLE HEADER.
+ * A Section should not contain others Sections and headers are not Sectionable!
*/
public class HeaderItem extends AbstractHeaderItem implements IFilterable {
- private static final long serialVersionUID = -7408637077727563374L;
-
private String id;
private String title;
private String subtitle;
@@ -88,7 +86,7 @@ public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater
@SuppressWarnings("unchecked")
public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "HeaderItem " + id + " Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "HeaderItem " + id + " Payload " + payloads);
} else {
holder.mTitle.setText(getTitle());
}
@@ -105,11 +103,11 @@ public boolean filter(String constraint) {
static class HeaderViewHolder extends FlexibleViewHolder {
- public TextView mTitle;
- public TextView mSubtitle;
- public ImageView mHandleView;
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mHandleView;
- public HeaderViewHolder(View view, FlexibleAdapter adapter) {
+ HeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter, true);//True for sticky
mTitle = (TextView) view.findViewById(R.id.title);
mSubtitle = (TextView) view.findViewById(R.id.subtitle);
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java
similarity index 94%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramHeaderItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java
index 9a0a86b6..85380739 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramHeaderItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramHeaderItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
@@ -73,7 +73,7 @@ public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater
@SuppressWarnings("unchecked")
public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "InstagramHeaderItem Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "InstagramHeaderItem Payload " + payloads);
} else {
holder.mTitle.setText(getTitle());
}
@@ -82,9 +82,9 @@ public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int
static class HeaderViewHolder extends FlexibleViewHolder {
- public FlipView mAccountImage;
- public TextView mTitle;
- public TextView mSubtitle;
+ FlipView mAccountImage;
+ TextView mTitle;
+ TextView mSubtitle;
public HeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter, true);//True for sticky
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java
similarity index 93%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java
index e1589d25..2214d6b3 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/InstagramItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/InstagramItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.content.Context;
import android.view.LayoutInflater;
@@ -86,11 +86,11 @@ public void bindViewHolder(final FlexibleAdapter adapter, ViewHolder holder, int
static final class ViewHolder extends FlexibleViewHolder {
- public ImageView mImage;
- public FlipView mImageFavourite;
- public ImageView mImageComment;
- public ImageView mImageShare;
- public TextView mQuantityLikes;
+ ImageView mImage;
+ FlipView mImageFavourite;
+ ImageView mImageComment;
+ ImageView mImageShare;
+ TextView mQuantityLikes;
public ViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/OverallItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/OverallItem.java
similarity index 98%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/OverallItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/OverallItem.java
index 7674cfda..6e9401df 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/OverallItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/OverallItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.graphics.drawable.Drawable;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java
new file mode 100644
index 00000000..e11a960c
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ProgressItem.java
@@ -0,0 +1,121 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.Payload;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.samples.flexibleadapter.items.ProgressItem.ProgressViewHolder;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * @author Davide Steduto
+ * @since 22/04/2016
+ */
+public class ProgressItem extends AbstractFlexibleItem {
+
+ private StatusEnum status = StatusEnum.MORE_TO_LOAD;
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;//The default implementation
+ }
+
+ public StatusEnum getStatus() {
+ return status;
+ }
+
+ public void setStatus(StatusEnum status) {
+ this.status = status;
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.progress_item;
+ }
+
+ @Override
+ public ProgressViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ProgressViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder,
+ int position, List payloads) {
+
+ Context context = holder.itemView.getContext();
+ holder.progressBar.setVisibility(View.GONE);
+ holder.progressMessage.setVisibility(View.VISIBLE);
+
+ if (!adapter.isEndlessScrollEnabled()) {
+ setStatus(StatusEnum.DISABLE_ENDLESS);
+ } else if (payloads.contains(Payload.NO_MORE_LOAD)) {
+ setStatus(StatusEnum.NO_MORE_LOAD);
+ }
+
+ switch (this.status) {
+ case NO_MORE_LOAD:
+ holder.progressMessage.setText(
+ context.getString(R.string.no_more_load_retry));
+ // Reset to default status for next binding
+ setStatus(StatusEnum.MORE_TO_LOAD);
+ break;
+ case DISABLE_ENDLESS:
+ holder.progressMessage.setText(context.getString(R.string.endless_disabled));
+ break;
+ case ON_CANCEL:
+ holder.progressMessage.setText(context.getString(R.string.endless_cancel));
+ // Reset to default status for next binding
+ setStatus(StatusEnum.MORE_TO_LOAD);
+ break;
+ case ON_ERROR:
+ holder.progressMessage.setText(context.getString(R.string.endless_error));
+ // Reset to default status for next binding
+ setStatus(StatusEnum.MORE_TO_LOAD);
+ break;
+ default:
+ holder.progressBar.setVisibility(View.VISIBLE);
+ holder.progressMessage.setVisibility(View.GONE);
+ break;
+ }
+ }
+
+ static class ProgressViewHolder extends FlexibleViewHolder {
+
+ @BindView(R.id.progress_bar)
+ ProgressBar progressBar;
+ @BindView(R.id.progress_message)
+ TextView progressMessage;
+
+ ProgressViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ ButterKnife.bind(this, view);
+ }
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.scaleAnimator(animators, itemView, 0f);
+ }
+ }
+
+ public enum StatusEnum {
+ MORE_TO_LOAD, //Default = should have an empty Payload
+ DISABLE_ENDLESS, //Endless is disabled because user has set limits
+ NO_MORE_LOAD, //Non-empty Payload = Payload.NO_MORE_LOAD
+ ON_CANCEL,
+ ON_ERROR
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableExpandableItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableExpandableItem.java
new file mode 100644
index 00000000..600bca7d
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableExpandableItem.java
@@ -0,0 +1,112 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.flexibleadapter.items.IExpandable;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.utils.Utils;
+import eu.davidea.viewholders.ExpandableViewHolder;
+
+import eu.davidea.samples.flexibleadapter.items.ScrollableExpandableItem.ScrollableExpandableViewHolder;
+
+/**
+ * Scrollable Header and Footer Item that can be expanded too. When visible, all the subItems
+ * will be Headers or Footers as well, depending where the parent has been initially added!
+ */
+public class ScrollableExpandableItem extends AbstractItem
+ implements IExpandable {
+
+ /* Flags for FlexibleAdapter */
+ private boolean mExpanded = false;
+
+ /* subItems list */
+ private List mSubItems;
+
+
+ public ScrollableExpandableItem(String id) {
+ super(id);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_scrollable_expandable_item;
+ }
+
+ @Override
+ public ScrollableExpandableViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ScrollableExpandableViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(FlexibleAdapter adapter, ScrollableExpandableViewHolder holder, int position, List payloads) {
+ holder.mTitle.setSelected(true);//For marquee!!
+ holder.mTitle.setText(Utils.fromHtmlCompat(getTitle()));
+ holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle()));
+
+ //Support for StaggeredGridLayoutManager
+ if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams()).setFullSpan(true);
+ Log.d("ScrollableExpandable", "ScrollableExpandableItem configured fullSpan for StaggeredGridLayout");
+ }
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return mExpanded;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ this.mExpanded = expanded;
+ }
+
+ @Override
+ public int getExpansionLevel() {
+ return 0;
+ }
+
+ @Override
+ public List getSubItems() {
+ return mSubItems;
+ }
+
+ public void addSubItem(ScrollableSubItem subItem) {
+ if (mSubItems == null)
+ mSubItems = new ArrayList<>();
+ mSubItems.add(subItem);
+ }
+
+ static class ScrollableExpandableViewHolder extends ExpandableViewHolder {
+
+ public TextView mTitle;
+ public TextView mSubtitle;
+
+ ScrollableExpandableViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter, true);
+ mTitle = (TextView) view.findViewById(R.id.title);
+ mSubtitle = (TextView) view.findViewById(R.id.subtitle);
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.slideInFromTopAnimator(animators, itemView, mAdapter.getRecyclerView());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollableExpandableItem[" + super.toString() + "]";
+ }
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableFooterItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableFooterItem.java
new file mode 100644
index 00000000..d52f5321
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableFooterItem.java
@@ -0,0 +1,117 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.utils.Utils;
+import eu.davidea.viewholders.AnimatedViewHolder;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * This item is a Scrollable Footer.
+ */
+public class ScrollableFooterItem extends AbstractItem {
+
+ public ScrollableFooterItem(String id) {
+ super(id);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_scrollable_footer_item;
+ }
+
+ @Override
+ public FooterViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new FooterViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(FlexibleAdapter adapter, FooterViewHolder holder, int position, List payloads) {
+ holder.mTitle.setText(Utils.fromHtmlCompat(getTitle()));
+ holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle()));
+ }
+
+ class FooterViewHolder extends FlexibleViewHolder implements AnimatedViewHolder {
+
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mDismissIcon;
+
+ FooterViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ mTitle = (TextView) view.findViewById(R.id.title);
+ mSubtitle = (TextView) view.findViewById(R.id.subtitle);
+ mDismissIcon = (ImageView) view.findViewById(R.id.dismiss_icon);
+ mDismissIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //Don't need anymore to set permanent deletion for Scrollable Headers and Footers
+ //mAdapter.setPermanentDelete(true);
+ //noinspection unchecked
+ mAdapter.removeScrollableFooter(ScrollableFooterItem.this);
+ //mAdapter.setPermanentDelete(false);
+ }
+ });
+
+ //Support for StaggeredGridLayoutManager
+ if (itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams()).setFullSpan(true);
+ }
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.slideInFromBottomAnimator(animators, itemView, mAdapter.getRecyclerView());
+ }
+
+ @Override
+ public boolean preAnimateAddImpl() {
+ ViewCompat.setTranslationY(itemView, itemView.getHeight());
+ ViewCompat.setAlpha(itemView, 0);
+ return true;
+ }
+
+ @Override
+ public boolean preAnimateRemoveImpl() {
+ return false;
+ }
+
+ @Override
+ public boolean animateAddImpl(ViewPropertyAnimatorListener listener, long addDuration, int index) {
+ ViewCompat.animate(itemView)
+ .translationY(0)
+ .alpha(1)
+ .setDuration(addDuration)
+ .setInterpolator(new DecelerateInterpolator())
+ .setListener(listener)
+ .start();
+ return true;
+ }
+
+ @Override
+ public boolean animateRemoveImpl(ViewPropertyAnimatorListener listener, long removeDuration, int index) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollableFooterItem[" + super.toString() + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java
new file mode 100644
index 00000000..59b49515
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableLayoutItem.java
@@ -0,0 +1,73 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.utils.Utils;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * Item dedicated to display which Layout is currently displayed.
+ * This item is a Scrollable Header.
+ */
+public class ScrollableLayoutItem extends AbstractItem {
+
+ public ScrollableLayoutItem(String id) {
+ super(id);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_scrollable_layout_item;
+ }
+
+ @Override
+ public LayoutViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new LayoutViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(FlexibleAdapter adapter, LayoutViewHolder holder, int position, List payloads) {
+ holder.mTitle.setText(Utils.fromHtmlCompat(getTitle()));
+ holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle()));
+
+ //Support for StaggeredGridLayoutManager
+ if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams()).setFullSpan(true);
+ Log.d("ScrollableLayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout");
+ }
+ }
+
+ public static class LayoutViewHolder extends FlexibleViewHolder {
+
+ public TextView mTitle;
+ public TextView mSubtitle;
+
+ public LayoutViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter, true);
+ mTitle = (TextView) view.findViewById(R.id.title);
+ mSubtitle = (TextView) view.findViewById(R.id.subtitle);
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.slideInFromTopAnimator(animators, itemView, mAdapter.getRecyclerView());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollableLayoutItem[" + super.toString() + "]";
+ }
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableSubItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableSubItem.java
new file mode 100644
index 00000000..71f77315
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableSubItem.java
@@ -0,0 +1,69 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * Scrollable SubHeader and SubFooter Item. When visible, will be a Header or a Footer,
+ * depending where the parent has been initially added!
+ */
+public class ScrollableSubItem extends AbstractItem {
+
+ public ScrollableSubItem(String id) {
+ super(id);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_scrollable_sub_item;
+ }
+
+ @Override
+ public ChildViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ChildViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void bindViewHolder(FlexibleAdapter adapter, ChildViewHolder holder, int position, List payloads) {
+ String title = "Scrollable SubItem " + adapter.getSubPositionOf(this);
+ holder.mTitle.setText(title);
+ }
+
+ /**
+ * Provide a reference to the views for each data item.
+ * Complex data labels may need more than one view per item, and
+ * you provide access to all the views for a data item in a view holder.
+ */
+ static final class ChildViewHolder extends FlexibleViewHolder {
+
+ TextView mTitle;
+
+ ChildViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ this.mTitle = (TextView) view.findViewById(R.id.title);
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.scaleAnimator(animators, itemView, 0f);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollableSubItem[" + super.toString() + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ULSItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java
similarity index 54%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ULSItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java
index ca18062d..904cf5e0 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ULSItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableULSItem.java
@@ -1,9 +1,8 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.support.annotation.NonNull;
import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -16,62 +15,49 @@
import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
import eu.davidea.samples.flexibleadapter.R;
import eu.davidea.samples.flexibleadapter.services.DatabaseConfiguration;
+import eu.davidea.utils.Utils;
import eu.davidea.viewholders.FlexibleViewHolder;
/**
- * Item dedicated only for User Learns Selection view (located always at position 0 in the Adapter).
- * If you don't have many fields in common better to extend directly from
- * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already
- * implemented methods (getter and setters).
+ * Item dedicated only for User Learns Selection view (located always at the top in the Adapter).
+ * This item is a Scrollable Header.
*/
-public class ULSItem extends AbstractModelItem {
+public class ScrollableULSItem extends AbstractItem {
- private static final long serialVersionUID = -5041296095060813327L;
-
- public ULSItem(String id) {
+ public ScrollableULSItem(String id) {
super(id);
}
- @Override
- public boolean isEnabled() {
- return false;
- }
-
- @Override
- public boolean isSelectable() {
- return false;
- }
-
@Override
public int getLayoutRes() {
- return R.layout.recycler_uls_item;
+ return R.layout.recycler_scrollable_uls_item;
}
@Override
- public ExampleViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ExampleViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ public ULSViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new ULSViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
}
@Override
- public void bindViewHolder(FlexibleAdapter adapter, ExampleViewHolder holder, int position, List payloads) {
+ public void bindViewHolder(FlexibleAdapter adapter, ULSViewHolder holder, int position, List payloads) {
holder.mImageView.setImageResource(R.drawable.ic_account_circle_white_24dp);
holder.itemView.setActivated(true);
- holder.mTitle.setSelected(true);//For marquee
- holder.mTitle.setText(Html.fromHtml(getTitle()));
- holder.mSubtitle.setText(Html.fromHtml(getSubtitle()));
+ holder.mTitle.setSelected(true);//For marquee!!
+ holder.mTitle.setText(Utils.fromHtmlCompat(getTitle()));
+ holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle()));
}
/**
* Used for UserLearnsSelection.
*/
- static class ExampleViewHolder extends FlexibleViewHolder {
+ class ULSViewHolder extends FlexibleViewHolder {
- public ImageView mImageView;
- public TextView mTitle;
- public TextView mSubtitle;
- public ImageView mDismissIcon;
+ ImageView mImageView;
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mDismissIcon;
- public ExampleViewHolder(View view, FlexibleAdapter adapter) {
+ ULSViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
mTitle = (TextView) view.findViewById(R.id.title);
mSubtitle = (TextView) view.findViewById(R.id.subtitle);
@@ -81,9 +67,11 @@ public ExampleViewHolder(View view, FlexibleAdapter adapter) {
@Override
public void onClick(View v) {
DatabaseConfiguration.userLearnedSelection = true;
- mAdapter.setPermanentDelete(true);
- mAdapter.removeItem(0);
- mAdapter.setPermanentDelete(false);
+ //Don't need anymore to set permanent deletion for Scrollable Headers and Footers
+ //mAdapter.setPermanentDelete(true);
+ //noinspection unchecked
+ mAdapter.removeScrollableHeader(ScrollableULSItem.this);
+ //mAdapter.setPermanentDelete(false);
}
});
@@ -101,7 +89,7 @@ public void scrollAnimators(@NonNull List animators, int position, boo
@Override
public String toString() {
- return "ULSItem[" + super.toString() + "]";
+ return "ScrollableULSItem[" + super.toString() + "]";
}
}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableUseCaseItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableUseCaseItem.java
new file mode 100644
index 00000000..d531c3cd
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/ScrollableUseCaseItem.java
@@ -0,0 +1,94 @@
+package eu.davidea.samples.flexibleadapter.items;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
+import eu.davidea.flexibleadapter.utils.DrawableUtils;
+import eu.davidea.samples.flexibleadapter.R;
+import eu.davidea.utils.Utils;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+/**
+ * This item is a Scrollable Header.
+ */
+public class ScrollableUseCaseItem extends AbstractItem {
+
+ public ScrollableUseCaseItem(String title, String subTitle) {
+ super("UC");
+ setTitle(title);
+ setSubtitle(subTitle);
+ }
+
+ @Override
+ public int getLayoutRes() {
+ return R.layout.recycler_scrollable_usecase_item;
+ }
+
+ @Override
+ public UCViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new UCViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ }
+
+ @Override
+ public void bindViewHolder(FlexibleAdapter adapter, UCViewHolder holder, int position, List payloads) {
+ Context context = holder.itemView.getContext();
+ DrawableUtils.setBackgroundCompat(holder.itemView, DrawableUtils.getRippleDrawable(
+ DrawableUtils.getColorDrawable(context.getResources().getColor(R.color.material_color_blue_grey_50)),
+ DrawableUtils.getColorControlHighlight(context))
+ );
+ holder.mTitle.setText(Utils.fromHtmlCompat(getTitle()));
+ holder.mSubtitle.setText(Utils.fromHtmlCompat(getSubtitle()));
+ }
+
+ public static class UCViewHolder extends FlexibleViewHolder {
+
+ public TextView mTitle;
+ public TextView mSubtitle;
+ ImageView mDismissIcon;
+
+ public UCViewHolder(View view, FlexibleAdapter adapter) {
+ super(view, adapter);
+ mTitle = (TextView) view.findViewById(R.id.title);
+ mSubtitle = (TextView) view.findViewById(R.id.subtitle);
+ mDismissIcon = (ImageView) view.findViewById(R.id.dismiss_icon);
+ mDismissIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //Don't need anymore to set permanent deletion for Scrollable Headers and Footers
+ //mAdapter.setPermanentDelete(true);
+ //noinspection unchecked, ConstantConditions
+ mAdapter.removeScrollableHeader(mAdapter.getItem(getAdapterPosition()));
+ //mAdapter.setPermanentDelete(false);
+ }
+ });
+
+ //Support for StaggeredGridLayoutManager
+ if (itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams()).setFullSpan(true);
+ }
+ }
+
+ @Override
+ public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
+ AnimatorHelper.flipAnimator(animators, itemView);
+ AnimatorHelper.setDuration(animators, 500L);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollableUseCaseItem[" + super.toString() + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SimpleItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java
similarity index 57%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SimpleItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java
index 2a973054..76c56cdc 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SimpleItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SimpleItem.java
@@ -1,11 +1,13 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.util.Log;
+import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -18,30 +20,26 @@
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
-import eu.davidea.flexibleadapter.items.IExpandable;
import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.ISectionable;
+import eu.davidea.flexibleadapter.utils.DrawableUtils;
import eu.davidea.flexibleadapter.utils.Utils;
import eu.davidea.flipview.FlipView;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.viewholders.ExpandableViewHolder;
+import eu.davidea.viewholders.FlexibleViewHolder;
/**
* You should extend directly from
* {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already
* implemented methods (getter and setters).
*/
-public class SimpleItem extends AbstractModelItem
- implements ISectionable, IFilterable, Serializable {
+public class SimpleItem extends AbstractItem
+ implements ISectionable, IFilterable, Serializable {
- private static final long serialVersionUID = -6882745111884490060L;
-
- /**
- * The header of this item
- */
+ /* The header of this item */
HeaderItem header;
- public SimpleItem(String id) {
+ private SimpleItem(String id) {
super(id);
setDraggable(true);
setSwipeable(true);
@@ -52,6 +50,13 @@ public SimpleItem(String id, HeaderItem header) {
this.header = header;
}
+ @Override
+ public String getSubtitle() {
+ return getId()
+ + (getHeader() != null ? " - " + getHeader().getId() : "")
+ + (getUpdates() > 0 ? " - u" + getUpdates() : "");
+ }
+
@Override
public HeaderItem getHeader() {
return header;
@@ -64,67 +69,44 @@ public void setHeader(HeaderItem header) {
@Override
public int getLayoutRes() {
- return R.layout.recycler_expandable_item;
+ return R.layout.recycler_simple_item;
}
@Override
- public ParentViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ParentViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
+ public SimpleViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
+ return new SimpleViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
}
@Override
- @SuppressWarnings({"unchecked", "deprecation"})
- public void bindViewHolder(final FlexibleAdapter adapter, ParentViewHolder holder, int position, List payloads) {
- //Subtitle
- if (adapter.isExpandable(this)) {
- setSubtitle(adapter.getCurrentChildren((IExpandable) this).size() + " subItems");
- } else {
- setSubtitle(getId());
- }
- setSubtitle(getSubtitle() + (getHeader() != null ? " - " + getHeader().getId() : ""));
-
+ @SuppressWarnings({"unchecked"})
+ public void bindViewHolder(final FlexibleAdapter adapter, SimpleViewHolder holder, int position, List payloads) {
Context context = holder.itemView.getContext();
- int defColorAccent = context.getResources().getColor(R.color.colorAccent_light);
- if (adapter.isExpandable(this) && payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "ExpandableItem Payload " + payloads);
- if (adapter.hasSearchText()) {
- Utils.highlightText(holder.itemView.getContext(), holder.mSubtitle,
- getSubtitle(), adapter.getSearchText(), defColorAccent);
- } else {
- holder.mSubtitle.setText(getSubtitle());
- }
- //We stop the process here, we only want to update the subtitle
+ // Background, when bound the first time
+ if (payloads.size() == 0) {
+ Drawable drawable = DrawableUtils.getSelectableBackgroundCompat(
+ Color.WHITE, Color.parseColor("#dddddd"), //Same color of divider
+ DrawableUtils.getColorControlHighlight(context));
+ DrawableUtils.setBackgroundCompat(holder.itemView, drawable);
+ DrawableUtils.setBackgroundCompat(holder.frontView, drawable);
+ }
+ // DemoApp: INNER ANIMATION EXAMPLE! ImageView - Handle Flip Animation
+// if (adapter.isSelectAll() || adapter.isLastItemInActionMode()) {
+// // Consume the Animation
+// holder.mFlipView.flip(adapter.isSelected(position), 200L);
+// } else {
+ // Display the current flip status
+ holder.mFlipView.flipSilently(adapter.isSelected(position));
+// }
+
+ // In case of searchText matches with Title or with a field this will be highlighted
+ if (adapter.hasSearchText()) {
+ Utils.highlightText(holder.mTitle, getTitle(), adapter.getSearchText());
+ Utils.highlightText(holder.mSubtitle, getSubtitle(), adapter.getSearchText());
} else {
- //DemoApp: INNER ANIMATION EXAMPLE! ImageView - Handle Flip Animation on Select ALL
- // and Deselect ALL
- if (adapter.isSelectAll() || adapter.isLastItemInActionMode()) {
- //Reset the flags with delay
- holder.itemView.postDelayed(new Runnable() {
- @Override
- public void run() {
- adapter.resetActionModeFlags();
- }
- }, 200L);
- //Consume the Animation
- holder.mFlipView.flip(adapter.isSelected(position), 200L);
- } else {
- //Display the current flip status
- holder.mFlipView.flipSilently(adapter.isSelected(position));
- }
-
- //In case of searchText matches with Title or with an SimpleItem's field
- // this will be highlighted
- if (adapter.hasSearchText()) {
- Utils.highlightText(context, holder.mTitle,
- getTitle(), adapter.getSearchText(), defColorAccent);
- Utils.highlightText(context, holder.mSubtitle,
- getSubtitle(), adapter.getSearchText(), defColorAccent);
- } else {
- holder.mTitle.setText(getTitle());
- holder.mSubtitle.setText(getSubtitle());
- }
+ holder.mTitle.setText(getTitle());
+ holder.mSubtitle.setText(getSubtitle());
}
}
@@ -134,21 +116,20 @@ public boolean filter(String constraint) {
getSubtitle() != null && getSubtitle().toLowerCase().trim().contains(constraint);
}
- /**
- * This ViewHolder is expandable and collapsible.
- */
- static final class ParentViewHolder extends ExpandableViewHolder {
-
- public FlipView mFlipView;
- public TextView mTitle;
- public TextView mSubtitle;
- public ImageView mHandleView;
- public Context mContext;
- private View frontView;
- private View rearLeftView;
- private View rearRightView;
-
- public ParentViewHolder(View view, FlexibleAdapter adapter) {
+ static final class SimpleViewHolder extends FlexibleViewHolder {
+
+ FlipView mFlipView;
+ TextView mTitle;
+ TextView mSubtitle;
+ ImageView mHandleView;
+ Context mContext;
+ View frontView;
+ View rearLeftView;
+ View rearRightView;
+
+ public boolean swiped = false;
+
+ SimpleViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
this.mContext = view.getContext();
this.mTitle = (TextView) view.findViewById(R.id.title);
@@ -157,9 +138,11 @@ public ParentViewHolder(View view, FlexibleAdapter adapter) {
this.mFlipView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mAdapter.mItemLongClickListener.onItemLongClick(getAdapterPosition());
- Toast.makeText(mContext, "ImageClick on " + mTitle.getText() + " position " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
- toggleActivation();
+ if (mAdapter.mItemLongClickListener != null) {
+ mAdapter.mItemLongClickListener.onItemLongClick(getAdapterPosition());
+ Toast.makeText(mContext, "ImageClick on " + mTitle.getText() + " position " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
+ toggleActivation();
+ }
}
});
this.mHandleView = (ImageView) view.findViewById(R.id.row_handle);
@@ -193,9 +176,9 @@ public boolean onLongClick(View view) {
}
@Override
- protected void toggleActivation() {
+ public void toggleActivation() {
super.toggleActivation();
- //Here we use a custom Animation inside the ItemView
+ // Here we use a custom Animation inside the ItemView
mFlipView.flip(mAdapter.isSelected(getAdapterPosition()));
}
@@ -245,12 +228,17 @@ public void scrollAnimators(@NonNull List animators, int position, boo
AnimatorHelper.slideInFromLeftAnimator(animators, itemView, mAdapter.getRecyclerView(), 0.5f);
}
}
+
+ @Override
+ public void onItemReleased(int position) {
+ swiped = (mActionState == ItemTouchHelper.ACTION_STATE_SWIPE);
+ super.onItemReleased(position);
+ }
}
@Override
public String toString() {
- return this instanceof ExpandableItem ? super.toString() :
- "SimpleItem[" + super.toString() + "]";
+ return "SimpleItem[" + super.toString() + "]";
}
}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredHeaderItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java
similarity index 93%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredHeaderItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java
index 3df16546..ec10506b 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredHeaderItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredHeaderItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
@@ -62,7 +62,7 @@ public HeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater
@SuppressWarnings("unchecked")
public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) {
if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "StaggeredHeaderItem Payload " + payloads);
+ Log.d(this.getClass().getSimpleName(), "StaggeredHeaderItem Payload " + payloads);
} else {
String title = this.title + " (" + adapter.getSectionItems(this).size() + ")";
holder.title.setText(title);
@@ -73,8 +73,6 @@ static class HeaderViewHolder extends FlexibleViewHolder {
@BindView(R.id.title)
TextView title;
- @BindView(R.id.layout_elevation)
- View elevationView;
public HeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter, true);//True for sticky
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java
similarity index 93%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java
index 3a3c4453..d3aecb72 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.content.Context;
import android.graphics.Color;
@@ -83,7 +83,7 @@ public void setMergedItems(List mergedItems) {
public void mergeItem(StaggeredItem staggeredItem) {
if (mergedItems == null) {
- mergedItems = new ArrayList(1);
+ mergedItems = new ArrayList<>(1);
}
mergedItems.add(staggeredItem);
}
@@ -116,17 +116,15 @@ public ViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater infla
@Override
public void bindViewHolder(final FlexibleAdapter adapter, final ViewHolder holder, int position, List payloads) {
- Context context = holder.itemTextView.getContext();
+ Context context = holder.itemView.getContext();
//Item Id
holder.itemTextView.setText(toString());
//Item Status
holder.statusTextView.setText(status.getResId());
- DrawableUtils.setBackground(holder.itemView,
- DrawableUtils.getSelectableBackgroundCompat(
- Color.WHITE, status.getColor(),
- Utils.getColorAccent(holder.itemView.getContext())));
+ DrawableUtils.setBackgroundCompat(holder.itemView, DrawableUtils.getSelectableBackgroundCompat(
+ status.getColor(), Utils.getColorAccent(context), Color.WHITE));
//Blink after moving the item
for (Object payload : payloads) {
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItemStatus.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItemStatus.java
similarity index 94%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItemStatus.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItemStatus.java
index 39cc801f..f2722e9a 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/StaggeredItemStatus.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/StaggeredItemStatus.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.graphics.Color;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SubItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java
similarity index 94%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SubItem.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java
index a3f0f1ff..5576859a 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/SubItem.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/items/SubItem.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter.models;
+package eu.davidea.samples.flexibleadapter.items;
import android.animation.Animator;
import android.content.Context;
@@ -25,11 +25,9 @@
* {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already
* implemented methods (getter and setters).
*/
-public class SubItem extends AbstractModelItem
+public class SubItem extends AbstractItem
implements ISectionable, IFilterable {
- private static final long serialVersionUID = 2519281529221244210L;
-
/**
* The header of this item
*/
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModel.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModel.java
new file mode 100644
index 00000000..10482a26
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/AbstractModel.java
@@ -0,0 +1,76 @@
+package eu.davidea.samples.flexibleadapter.models;
+
+import java.io.Serializable;
+import eu.davidea.flexibleadapter.items.IHolder;
+
+/**
+ * This class is Pojo for the {@link IHolder} items.
+ * It is used as base item for Item and Header model examples.
+ *
+ * Using Holder pattern, you can implement DB, XML & JSON (de)serialization libraries on this
+ * item as usual.
+ *
+ * @author Davide Steduto
+ * @since 19/10/2016
+ */
+public abstract class AbstractModel implements Serializable {
+
+ private static final long serialVersionUID = -7385882749119849060L;
+
+ private String id;
+ private String title;
+ private String subtitle;
+
+ public AbstractModel(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean equals(Object inObject) {
+ if (inObject instanceof AbstractModel) {
+ AbstractModel inItem = (AbstractModel) inObject;
+ return this.id.equals(inItem.id);
+ }
+ return false;
+ }
+
+ /**
+ * Override this method too, when using functionalities like StableIds, Filter or CollapseAll.
+ * FlexibleAdapter is making use of HashSet to improve performance, especially in big list.
+ */
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getSubtitle() {
+ return subtitle;
+ }
+
+ public void setSubtitle(String subtitle) {
+ this.subtitle = subtitle;
+ }
+
+ @Override
+ public String toString() {
+ return "id=" + id +
+ ", title=" + title;
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableItem.java
deleted file mode 100644
index 0e947a84..00000000
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ExpandableItem.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package eu.davidea.samples.flexibleadapter.models;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import eu.davidea.flexibleadapter.items.IExpandable;
-
-/**
- * If you don't have fields in common (my example: SimpleItem) better to extend directly from
- * {@link eu.davidea.flexibleadapter.items.AbstractExpandableItem} to benefit of the already
- * implemented methods around subItems list.
- */
-public class ExpandableItem extends SimpleItem
- implements IExpandable {
-
- private static final long serialVersionUID = -6882745111884490060L;
-
- /* Flags for FlexibleAdapter */
- private boolean mExpanded = false;
-
- /* subItems list */
- private List mSubItems;
-
-
- public ExpandableItem(String id) {
- super(id);
- }
-
- public ExpandableItem(String id, HeaderItem header) {
- super(id, header);
- }
-
- @Override
- public boolean isExpanded() {
- return mExpanded;
- }
-
- @Override
- public void setExpanded(boolean expanded) {
- mExpanded = expanded;
- }
-
- @Override
- public int getExpansionLevel() {
- return 0;
- }
-
- @Override
- public List getSubItems() {
- return mSubItems;
- }
-
- public final boolean hasSubItems() {
- return mSubItems!= null && mSubItems.size() > 0;
- }
-
- public boolean removeSubItem(SubItem item) {
- return item != null && mSubItems.remove(item);
- }
-
- public boolean removeSubItem(int position) {
- if (mSubItems != null && position >= 0 && position < mSubItems.size()) {
- mSubItems.remove(position);
- return true;
- }
- return false;
- }
-
- public void addSubItem(SubItem subItem) {
- if (mSubItems == null)
- mSubItems = new ArrayList();
- mSubItems.add(subItem);
- }
-
- public void addSubItem(int position, SubItem subItem) {
- if (mSubItems != null && position >= 0 && position < mSubItems.size()) {
- mSubItems.add(position, subItem);
- } else
- addSubItem(subItem);
- }
-
-// @Override
-// public int getLayoutRes() {
-// return R.layout.recycler_expandable_row;
-// }
-
- @Override
- public String toString() {
- return "ExpandableItem[" + super.toString() + "//SubItems" + mSubItems + "]";
- }
-
-}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderModel.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderModel.java
new file mode 100644
index 00000000..2e82d9c0
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/HeaderModel.java
@@ -0,0 +1,15 @@
+package eu.davidea.samples.flexibleadapter.models;
+
+/**
+ * Model item for HeaderHolder.
+ *
+ * @author Davide Steduto
+ * @since 19/10/2016
+ */
+public class HeaderModel extends AbstractModel {
+
+ public HeaderModel(String id) {
+ super(id);
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemModel.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemModel.java
new file mode 100644
index 00000000..22b8c93c
--- /dev/null
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ItemModel.java
@@ -0,0 +1,15 @@
+package eu.davidea.samples.flexibleadapter.models;
+
+/**
+ * Model item for ItemHolder.
+ *
+ * @author Davide Steduto
+ * @since 19/10/2016
+ */
+public class ItemModel extends AbstractModel {
+
+ public ItemModel(String id) {
+ super(id);
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/LayoutItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/LayoutItem.java
deleted file mode 100644
index 691fe031..00000000
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/LayoutItem.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package eu.davidea.samples.flexibleadapter.models;
-
-import android.animation.Animator;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import java.util.List;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
-import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-/**
- * Item dedicated to display the status of the Layout currently displayed (located always at
- * position 0 in the Adapter).
- *
- * If you don't have many fields in common better to extend directly from
- * {@link eu.davidea.flexibleadapter.items.AbstractFlexibleItem} to benefit of the already
- * implemented methods (getter and setters).
- */
-public class LayoutItem extends AbstractModelItem {
-
- private static final long serialVersionUID = -5041296095060813327L;
-
- public LayoutItem(String id) {
- super(id);
- }
-
- @Override
- public boolean isEnabled() {
- return false;
- }
-
- @Override
- public boolean isSelectable() {
- return false;
- }
-
- @Override
- public int getLayoutRes() {
- return R.layout.recycler_layout_item;
- }
-
- @Override
- public ExampleViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ExampleViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
- }
-
- @Override
- public void bindViewHolder(FlexibleAdapter adapter, ExampleViewHolder holder, int position, List payloads) {
- holder.mTitle.setSelected(true);//For marquee
- holder.mTitle.setText(getTitle());
- holder.mSubtitle.setText(getSubtitle());
-
- //Support for StaggeredGridLayoutManager
- if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
- ((StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams()).setFullSpan(true);
- Log.d("LayoutItem", "LayoutItem configured fullSpan for StaggeredGridLayout");
- }
- }
-
- /**
- * Used for UserLearnsSelection.
- */
- public static class ExampleViewHolder extends FlexibleViewHolder {
-
- public TextView mTitle;
- public TextView mSubtitle;
-
- public ExampleViewHolder(View view, FlexibleAdapter adapter) {
- super(view, adapter, true);
- mTitle = (TextView) view.findViewById(R.id.title);
- mSubtitle = (TextView) view.findViewById(R.id.subtitle);
- }
-
- @Override
- public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
- AnimatorHelper.slideInFromTopAnimator(animators, itemView, mAdapter.getRecyclerView());
- }
- }
-
- @Override
- public String toString() {
- return "LayoutItem[" + super.toString() + "]";
- }
-}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ProgressItem.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ProgressItem.java
deleted file mode 100644
index fa7aeb1c..00000000
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/models/ProgressItem.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package eu.davidea.samples.flexibleadapter.models;
-
-import android.animation.Animator;
-import android.support.annotation.NonNull;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ProgressBar;
-
-import java.util.List;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.helpers.AnimatorHelper;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.ProgressItem.ProgressViewHolder;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-/**
- * @author Davide Steduto
- * @since 22/04/2016
- */
-public class ProgressItem extends AbstractFlexibleItem {
-
- @Override
- public boolean equals(Object o) {
- return this == o;//The default implementation
- }
-
- @Override
- public int getLayoutRes() {
- return R.layout.progress_item;
- }
-
- @Override
- public ProgressViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ProgressViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
- }
-
- @Override
- public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder, int position, List payloads) {
- //nothing to bind
- }
-
- public static class ProgressViewHolder extends FlexibleViewHolder {
-
- public ProgressBar progressBar;
-
- public ProgressViewHolder(View view, FlexibleAdapter adapter) {
- super(view, adapter);
- progressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
- }
- @Override
- public void scrollAnimators(@NonNull List animators, int position, boolean isForward) {
- AnimatorHelper.scaleAnimator(animators, itemView, 0f);
- }
- }
-
-}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseConfiguration.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseConfiguration.java
index 3ed8868d..a4d0fef5 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseConfiguration.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseConfiguration.java
@@ -18,11 +18,12 @@ public class DatabaseConfiguration {
NOTIFY_MOVE = "notify_move";
//Values
- public static int maxSize = 100000;//max number of items
+ public static int maxSize = 10000;//max number of items
public static int maxSearchDelay = 1000;//max search delay in ms
- public static int size = 10000;//items
+ public static int size = 1000;//items
public static int delay = 200;//ms
public static int animateToLimit = maxSize;//start with maxSize
+ public static boolean animateWithDiffUtil = false;
public static boolean notifyChange = true;
public static boolean notifyMove = false;
public static boolean animateOnScrolling = true;
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java
index c3238f35..aa04412a 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/services/DatabaseService.java
@@ -17,23 +17,27 @@
import eu.davidea.flexibleadapter.items.IFlexible;
import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.AbstractModelItem;
-import eu.davidea.samples.flexibleadapter.models.AnimatorExpandableItem;
-import eu.davidea.samples.flexibleadapter.models.AnimatorSubItem;
-import eu.davidea.samples.flexibleadapter.models.ConfigurationItem;
-import eu.davidea.samples.flexibleadapter.models.ExpandableHeaderItem;
-import eu.davidea.samples.flexibleadapter.models.ExpandableItem;
-import eu.davidea.samples.flexibleadapter.models.ExpandableLevel0Item;
-import eu.davidea.samples.flexibleadapter.models.ExpandableLevel1Item;
-import eu.davidea.samples.flexibleadapter.models.HeaderItem;
-import eu.davidea.samples.flexibleadapter.models.InstagramHeaderItem;
-import eu.davidea.samples.flexibleadapter.models.InstagramItem;
-import eu.davidea.samples.flexibleadapter.models.OverallItem;
-import eu.davidea.samples.flexibleadapter.models.SimpleItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredHeaderItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredItem;
-import eu.davidea.samples.flexibleadapter.models.StaggeredItemStatus;
-import eu.davidea.samples.flexibleadapter.models.SubItem;
+import eu.davidea.samples.flexibleadapter.items.AbstractItem;
+import eu.davidea.samples.flexibleadapter.items.AnimatorExpandableItem;
+import eu.davidea.samples.flexibleadapter.items.AnimatorSubItem;
+import eu.davidea.samples.flexibleadapter.items.ConfigurationItem;
+import eu.davidea.samples.flexibleadapter.items.ExpandableHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.ExpandableItem;
+import eu.davidea.samples.flexibleadapter.items.ExpandableLevel0Item;
+import eu.davidea.samples.flexibleadapter.items.ExpandableLevel1Item;
+import eu.davidea.samples.flexibleadapter.holders.HeaderHolder;
+import eu.davidea.samples.flexibleadapter.items.HeaderItem;
+import eu.davidea.samples.flexibleadapter.models.HeaderModel;
+import eu.davidea.samples.flexibleadapter.items.InstagramHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.InstagramItem;
+import eu.davidea.samples.flexibleadapter.holders.ItemHolder;
+import eu.davidea.samples.flexibleadapter.models.ItemModel;
+import eu.davidea.samples.flexibleadapter.items.OverallItem;
+import eu.davidea.samples.flexibleadapter.items.SimpleItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredHeaderItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredItem;
+import eu.davidea.samples.flexibleadapter.items.StaggeredItemStatus;
+import eu.davidea.samples.flexibleadapter.items.SubItem;
/**
* Created by Davide Steduto on 23/11/2015.
@@ -51,7 +55,7 @@ public class DatabaseService {
private Map headers;
- DatabaseService() {
+ private DatabaseService() {
}
public static DatabaseService getInstance() {
@@ -77,6 +81,8 @@ public DatabaseType getDatabaseType() {
* List of CardView as entry list, showing the functionality of the library.
* It also shows how adapter animation can be configured.
*/
+ //TODO: Review the description of all examples
+ //TODO: Add ScrollableUseCaseItem header for each database
public void createOverallDatabase(Resources resources) {
databaseType = DatabaseType.OVERALL;
mItems.clear();
@@ -116,12 +122,15 @@ public void createOverallDatabase(Resources resources) {
mItems.add(new OverallItem(R.id.nav_model_holders, resources.getString(R.string.model_holders))
.withDescription(resources.getString(R.string.model_holders_description))
- .withIcon(resources.getDrawable(R.drawable.ic_select_inverse_grey600_24dp))
- .withEnabled(false));
+ .withIcon(resources.getDrawable(R.drawable.ic_select_inverse_grey600_24dp)));
mItems.add(new OverallItem(R.id.nav_staggered, resources.getString(R.string.staggered_layout))
.withDescription(resources.getString(R.string.staggered_description))
.withIcon(resources.getDrawable(R.drawable.ic_dashboard_grey600_24dp)));
+
+ mItems.add(new OverallItem(R.id.nav_viewpager, resources.getString(R.string.viewpager))
+ .withDescription(resources.getString(R.string.viewpager_description))
+ .withIcon(resources.getDrawable(R.drawable.ic_view_carousel_grey600_24dp)));
}
/*
@@ -203,6 +212,21 @@ public void createHeadersSectionsDatabase(int size, int headers) {
}
}
+ /*
+ * List of Holder Items and Header. Only Holder Simple Items will be
+ * added to the list. IHolder items hold the model data inside.
+ */
+ public void createHolderSectionsDatabase(int size, int headers) {
+ databaseType = DatabaseType.MODEL_HOLDERS;
+ HeaderHolder header = null;
+ mItems.clear();
+ int lastHeaderId = 0;
+ for (int i = 0; i < size; i++) {
+ header = i % Math.round(size / headers) == 0 ? newHeaderHolder(++lastHeaderId) : header;
+ mItems.add(newItemHolder(i + 1, header));
+ }
+ }
+
/*
* List of Expandable items (headers/sections) with SubItems with Header attached.
*/
@@ -387,6 +411,19 @@ public static StaggeredItem newStaggeredItem(int i, StaggeredHeaderItem header)
return new StaggeredItem(i, header);
}
+ private HeaderHolder newHeaderHolder(int i) {
+ HeaderModel model = new HeaderModel("H" + i);
+ model.setTitle("Header " + i);
+ return new HeaderHolder(model);
+ }
+
+ private ItemHolder newItemHolder(int i, HeaderHolder header) {
+ ItemModel model = new ItemModel("I" + i);
+ model.setTitle("Holder Item " + i);
+ model.setSubtitle("Subtitle " + i);
+ return new ItemHolder(model, header);
+ }
+
/*-----------------------*/
/* MAIN DATABASE METHODS */
/*-----------------------*/
@@ -424,7 +461,7 @@ public void addAll(List newItems) {
mItems.addAll(newItems);
}
- public void addItem(int position, AbstractModelItem item) {
+ public void addItem(int position, AbstractItem item) {
if (position < mItems.size())
mItems.add(position, item);
else
@@ -443,7 +480,7 @@ public void addItem(AbstractFlexibleItem item) {
public void addSubItem(int position, IExpandable parent, SubItem subItem) {
//This split is for my examples
if (parent instanceof ExpandableItem)
- ((ExpandableItem) parent).removeSubItem(subItem);
+ ((ExpandableItem) parent).addSubItem(subItem);
else if (parent instanceof ExpandableHeaderItem)
((ExpandableHeaderItem) parent).addSubItem(subItem);
}
@@ -524,6 +561,19 @@ public void splitItem(StaggeredItem mainItem, StaggeredItem itemToSplit) {
Collections.sort(mItems, new ItemComparatorById());
}
+ /**
+ * This demonstrates that new content of existing items are really rebound and
+ * notified with CHANGE Payload in the Adapter list when refreshed.
+ */
+ public void updateNewItems() {
+ for (IFlexible iFlexible : mItems) {
+ if (iFlexible instanceof AbstractItem) {
+ AbstractItem item = (AbstractItem) iFlexible;
+ item.increaseUpdates();
+ }
+ }
+ }
+
/**
* A simple item comparator by Id.
*/
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/ExpandableHeaderItemExample.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/ExpandableHeaderItemExample.java
deleted file mode 100644
index ae62c3c5..00000000
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/ExpandableHeaderItemExample.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2016 Davide Steduto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package eu.davidea.samples.flexibleadapter.showcase;
-
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import java.io.Serializable;
-import java.util.List;
-
-import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.SubItem;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem;
-import eu.davidea.viewholders.ExpandableViewHolder;
-
-/**
- * This is an another example (not used in the demo) of how a Section with header can also be
- * expanded/collapsed.
- * The new object AbstractExpandableHeaderItem is an AbstractExpandableItem that implements IHeader.
- * It's important to note that, the ViewHolder must be specified in all <diamond> signature.
- */
-public class ExpandableHeaderItemExample
- extends AbstractExpandableHeaderItem
- implements Serializable {
-
- private static final long serialVersionUID = -1882711111814491060L;
-
- private String id;
- private String title;
- private String subtitle;
-
- public ExpandableHeaderItemExample(String id) {
- super();//Call super to auto-configure the section status as shown, expanded, not selectable
- this.id = id;
- }
-
- @Override
- public boolean equals(Object inObject) {
- if (inObject instanceof ExpandableHeaderItemExample) {
- ExpandableHeaderItemExample inItem = (ExpandableHeaderItemExample) inObject;
- return this.id.equals(inItem.id);
- }
- return false;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getSubtitle() {
- return subtitle;
- }
-
- public void setSubtitle(String subtitle) {
- this.subtitle = subtitle;
- }
-
- @Override
- public int getLayoutRes() {
- return R.layout.recycler_expandable_header_item;
- }
-
- @Override
- public ExpandableHeaderViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ExpandableHeaderViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
- }
-
- @Override
- public void bindViewHolder(FlexibleAdapter adapter, ExpandableHeaderViewHolder holder, int position, List payloads) {
- if (payloads.size() > 0) {
- Log.i(this.getClass().getSimpleName(), "ExpandableHeaderItem Payload " + payloads);
- } else {
- holder.mTitle.setText(getTitle());
- }
- setSubtitle(adapter.getCurrentChildren(this).size() + " subItems");
- holder.mSubtitle.setText(getSubtitle());
- }
-
- /**
- * Provide a reference to the views for each data item.
- * Complex data labels may need more than one view per item, and
- * you provide access to all the views for a data item in a view holder.
- */
- public static class ExpandableHeaderViewHolder extends ExpandableViewHolder {
-
- public TextView mTitle;
- public TextView mSubtitle;
-
- public ExpandableHeaderViewHolder(View view, FlexibleAdapter adapter) {
- super(view, adapter);
- mTitle = (TextView) view.findViewById(R.id.title);
- mSubtitle = (TextView) view.findViewById(R.id.subtitle);
- }
-
- @Override
- protected boolean isViewExpandableOnClick() {
- return true;
- }
- }
-
- @Override
- public String toString() {
- return "ExpandableHeaderItem[" + super.toString() + "//SubItems" + mSubItems + "]";
- }
-
-}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/FlexibleItemHolderExample.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/FlexibleItemHolderExample.java
deleted file mode 100644
index 0661f31d..00000000
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/showcase/FlexibleItemHolderExample.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2016 Davide Steduto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package eu.davidea.samples.flexibleadapter.showcase;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.List;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractSectionableItem;
-import eu.davidea.flexibleadapter.items.IFilterable;
-import eu.davidea.flexibleadapter.items.IHolder;
-import eu.davidea.samples.flexibleadapter.R;
-import eu.davidea.samples.flexibleadapter.models.HeaderItem;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-/**
- * In case you need to display the same modelData in multiple RecyclerViews managed by different
- * Adapters, you can implement a derived IFlexible item to HOLD your data model object!
- *
- * In this way you can separate the memory zones of the flags (enabled, expanded, hidden, selectable,
- * draggable, swipeable, etc...) used by an Adapter, to be independent by another Adapter.
- * For instance an item can be Shown and Expanded in a RV, while in the other RV can be Hidden or
- * Not Expanded!
- */
-public class FlexibleItemHolderExample extends AbstractSectionableItem
- implements IFilterable, IHolder {
-
- /**
- * Your complex data model object
- */
- Model modelData;
-
- public FlexibleItemHolderExample(Model modelData, HeaderItem header) {
- super(header);
- this.modelData = modelData;
- }
-
- @Override
- public Model getModel() {
- return modelData;
- }
-
- @Override
- public boolean equals(Object o) {
- //TODO FOR YOU: Implement the equals() also for the custom Model object
- if (o instanceof FlexibleItemHolderExample) {
- FlexibleItemHolderExample inItem = (FlexibleItemHolderExample) o;
- return modelData.equals(inItem.getModel());
- }
- return false;
- }
-
- @Override
- public int getLayoutRes() {
- return R.layout.recycler_expandable_item;
- }
-
- @Override
- public ViewHolder createViewHolder(FlexibleAdapter adapter, LayoutInflater inflater, ViewGroup parent) {
- return new ViewHolder(inflater.inflate(getLayoutRes(), parent, false), adapter);
- }
-
- @Override
- public void bindViewHolder(final FlexibleAdapter adapter, ViewHolder holder, int position, List payloads) {
- //TODO FOR YOU: Bind your VH, data comes from your Model object, so modelData always call a getter method
- //holder.titleTextView.setText(modelData.getTitle());
- }
-
- @Override
- public boolean filter(String constraint) {
- //TODO FOR YOU: Customize your filter logic, filtering is done on Model object data such as getTitle()
- //return modelData.getTitle().equals(constraint);
- return true;
- }
-
- public static final class ViewHolder extends FlexibleViewHolder {
-
- public ViewHolder(View view, FlexibleAdapter adapter) {
- super(view, adapter);
- //TODO FOR YOU: Initialize the Views
- }
- }
-
-}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/HeaderView.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/HeaderView.java
similarity index 93%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/HeaderView.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/HeaderView.java
index 4a22866a..72df197e 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/HeaderView.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/HeaderView.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter;
+package eu.davidea.samples.flexibleadapter.views;
import android.annotation.TargetApi;
import android.content.Context;
@@ -9,6 +9,7 @@
import butterknife.BindView;
import butterknife.ButterKnife;
+import eu.davidea.samples.flexibleadapter.R;
public class HeaderView extends LinearLayout {
diff --git a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ProgressBar.java b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/ProgressBar.java
similarity index 98%
rename from flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ProgressBar.java
rename to flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/ProgressBar.java
index b728042e..9579fb66 100644
--- a/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/ProgressBar.java
+++ b/flexible-adapter-app/src/main/java/eu/davidea/samples/flexibleadapter/views/ProgressBar.java
@@ -1,4 +1,4 @@
-package eu.davidea.samples.flexibleadapter;
+package eu.davidea.samples.flexibleadapter.views;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -10,6 +10,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import eu.davidea.samples.flexibleadapter.R;
+
/**
* Procedurally-drawn version of a horizontal indeterminate progress bar. Draws faster and more
* frequently (by making use of the animation timer), requires minimal memory overhead, and allows
diff --git a/flexible-adapter-app/src/main/res/drawable/myrect.xml b/flexible-adapter-app/src/main/res/drawable/myrect.xml
new file mode 100644
index 00000000..8b3f8800
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/drawable/myrect.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/activity_view_pager.xml b/flexible-adapter-app/src/main/res/layout/activity_view_pager.xml
new file mode 100644
index 00000000..6d129c7f
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/activity_view_pager.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/fragment_recycler_view.xml b/flexible-adapter-app/src/main/res/layout/fragment_recycler_view.xml
index 88adac21..ecd876ed 100644
--- a/flexible-adapter-app/src/main/res/layout/fragment_recycler_view.xml
+++ b/flexible-adapter-app/src/main/res/layout/fragment_recycler_view.xml
@@ -10,10 +10,11 @@
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:enabled="true">
+ android:enabled="false">
-
-
-
+
+
-
-
-
diff --git a/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml
new file mode 100644
index 00000000..b5fbeecd
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/fragment_view_pager.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/progress_bar.xml b/flexible-adapter-app/src/main/res/layout/progress_bar.xml
index 41cf25b7..4c1990d3 100644
--- a/flexible-adapter-app/src/main/res/layout/progress_bar.xml
+++ b/flexible-adapter-app/src/main/res/layout/progress_bar.xml
@@ -1,5 +1,5 @@
-
-
+ android:layout_gravity="center"/>
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_animator_sub_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_animator_sub_item.xml
index 54993f80..149ad08a 100644
--- a/flexible-adapter-app/src/main/res/layout/recycler_animator_sub_item.xml
+++ b/flexible-adapter-app/src/main/res/layout/recycler_animator_sub_item.xml
@@ -6,7 +6,7 @@
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingTop="@dimen/margin_top"
android:paddingBottom="@dimen/margin_bottom"
- android:background="@drawable/selector_item_grey">
+ android:background="?attr/selectableItemBackground">
+ android:paddingTop="@dimen/margin_top">
+ android:background="#aa7e57c2"
+ android:elevation="5dp">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_holder_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_holder_item.xml
new file mode 100644
index 00000000..c5d1a762
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_holder_item.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_label_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_overall_item.xml
similarity index 100%
rename from flexible-adapter-app/src/main/res/layout/recycler_label_item.xml
rename to flexible-adapter-app/src/main/res/layout/recycler_overall_item.xml
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_expandable_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_expandable_item.xml
new file mode 100644
index 00000000..02f6fa81
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_expandable_item.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml
new file mode 100644
index 00000000..718b5708
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_footer_item.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_header_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_header_item.xml
new file mode 100644
index 00000000..f08a76cc
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_header_item.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_layout_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_layout_item.xml
similarity index 93%
rename from flexible-adapter-app/src/main/res/layout/recycler_layout_item.xml
rename to flexible-adapter-app/src/main/res/layout/recycler_scrollable_layout_item.xml
index 76c0eb91..8b4287de 100644
--- a/flexible-adapter-app/src/main/res/layout/recycler_layout_item.xml
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_layout_item.xml
@@ -16,9 +16,7 @@
android:layout_marginStart="@dimen/margin_left"
android:layout_marginRight="@dimen/margin_right"
android:layout_marginEnd="@dimen/margin_right"
- android:ellipsize="marquee"
- android:marqueeRepeatLimit="1"
- android:singleLine="true"
+ android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Button"
android:textColor="?primaryTextSelector"
tools:text="@string/staggered_layout"/>
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_scrollable_sub_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_sub_item.xml
new file mode 100644
index 00000000..2c3d43e2
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_sub_item.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_uls_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml
similarity index 93%
rename from flexible-adapter-app/src/main/res/layout/recycler_uls_item.xml
rename to flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml
index 3e0a0099..405c8588 100644
--- a/flexible-adapter-app/src/main/res/layout/recycler_uls_item.xml
+++ b/flexible-adapter-app/src/main/res/layout/recycler_scrollable_uls_item.xml
@@ -30,8 +30,8 @@
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
- android:layout_marginRight="@dimen/margin_small"
- android:layout_marginEnd="@dimen/margin_small">
+ android:layout_marginRight="@dimen/margin_right"
+ android:layout_marginEnd="@dimen/margin_right">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_simple_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_simple_item.xml
new file mode 100644
index 00000000..09140a05
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_simple_item.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_staggered_header_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_staggered_header_item.xml
index e49383fb..15f49915 100644
--- a/flexible-adapter-app/src/main/res/layout/recycler_staggered_header_item.xml
+++ b/flexible-adapter-app/src/main/res/layout/recycler_staggered_header_item.xml
@@ -5,28 +5,20 @@
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:layout_centerVertical="true"
- android:paddingBottom="2dp"
- android:clipToPadding="false">
+ android:paddingEnd="@dimen/margin_right"
+ android:paddingLeft="@dimen/margin_left"
+ android:paddingRight="@dimen/margin_right"
+ android:paddingStart="@dimen/margin_left"
+ android:background="@color/material_color_grey_50"
+ android:elevation="2dp">
-
-
-
-
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/recycler_vertical_item.xml b/flexible-adapter-app/src/main/res/layout/recycler_vertical_item.xml
new file mode 100644
index 00000000..298245be
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/layout/recycler_vertical_item.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/layout/toolbar_titles.xml b/flexible-adapter-app/src/main/res/layout/toolbar_titles.xml
index 81d136ae..98d70544 100644
--- a/flexible-adapter-app/src/main/res/layout/toolbar_titles.xml
+++ b/flexible-adapter-app/src/main/res/layout/toolbar_titles.xml
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/menu/activity_entry_drawer.xml b/flexible-adapter-app/src/main/res/menu/activity_entry_drawer.xml
index 6b9dd2cb..7f01eb44 100644
--- a/flexible-adapter-app/src/main/res/menu/activity_entry_drawer.xml
+++ b/flexible-adapter-app/src/main/res/menu/activity_entry_drawer.xml
@@ -42,7 +42,6 @@
android:title="@string/instagram_headers"/>
-
-
+
-
-
+ -
+
+
-
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/menu/menu_holders.xml b/flexible-adapter-app/src/main/res/menu/menu_holders.xml
new file mode 100644
index 00000000..ac6bf0f2
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/menu/menu_holders.xml
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/menu/menu_view_pager.xml b/flexible-adapter-app/src/main/res/menu/menu_view_pager.xml
new file mode 100644
index 00000000..c91d08a2
--- /dev/null
+++ b/flexible-adapter-app/src/main/res/menu/menu_view_pager.xml
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/values-v19/styles.xml b/flexible-adapter-app/src/main/res/values-v19/styles.xml
index 307a9cf4..cc16e793 100644
--- a/flexible-adapter-app/src/main/res/values-v19/styles.xml
+++ b/flexible-adapter-app/src/main/res/values-v19/styles.xml
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/flexible-adapter-app/src/main/res/values/dimens.xml b/flexible-adapter-app/src/main/res/values/dimens.xml
index 2947b9c3..c14164a3 100644
--- a/flexible-adapter-app/src/main/res/values/dimens.xml
+++ b/flexible-adapter-app/src/main/res/values/dimens.xml
@@ -39,5 +39,6 @@
40dp
14dp
14dp
+ 8dp
diff --git a/flexible-adapter-app/src/main/res/values/strings.xml b/flexible-adapter-app/src/main/res/values/strings.xml
index e9ec5f84..24143a37 100644
--- a/flexible-adapter-app/src/main/res/values/strings.xml
+++ b/flexible-adapter-app/src/main/res/values/strings.xml
@@ -18,6 +18,7 @@
Grid Layout
Staggered Layout
%1$s column(s)
+ Use DiffUtil*
Entry Animation Only
Scrolling Animation*
Reverse scrolling
@@ -34,9 +35,21 @@
Mode Single
Undo
-
+
Click on Image (or LongClick on Item) to select that Item]]>
- This is an example of 2nd view type]]>
+ This is a scrollable header.]]>
+ Scrollable Header Item
+ Scrollable Headers are always displayed at the top of all main items.]]>
+ Scrollable Expandable Header Item
+ When expanded, subItems will be added as headers too.]]>
+ Scrollable Expandable Footer Item
+ When expanded, subItems will be added as footers too.]]>
+ Scrollable Footer Item
+ Scrollable Footers are always displayed at the bottom of all main items.]]>
+ No more items to load. Refresh to retry.
+ No more items to load (max reached).
+ Cancelled by the user.
+ An error occurred while loading more items.
Title
@@ -60,6 +73,7 @@
STATUS E
Scrolling Animations
Item Animators
+ Complete action using
Configure the list ...
@@ -79,23 +93,41 @@ text back to normal. This happens systematically when searchText is reduced in l
Notify move of filtered items (Nicely animate the moved items)
The process is very slow on big list of the order of ~3-5000
-items and higher, due to calculate the correct position for each item to shift. Use with caution!
-
The slowness is higher when the searchText is cleared out. Try to disable to improve performance]]>
+items and higher, due to the calculation of the correct position for each item to shift. Use with caution!
+
The slowness is higher when the searchText is cleared out. Try to disable to improve performance.]]>
Overall
+ Showcase of the library capabilities
+ fragments where the
+RecyclerView and the Adapter are reinitialized. Each example implements several features of
+the library. Common listeners are instead implemented in the MainActivity.]]>
Selection modes
- IDLE, SINGLE, MULTI + ActionModeHelper + UndoHelper
+ IDLE, SINGLE, MULTI + ActionModeHelper + UndoHelper + Scrollable Headers
+ IDLE, SINGLE, MULTI
+ ActionModeHelper. You can experiment the MULTI selection starting from the SINGLE and from IDLE mode.
+Screen rotation is also supported. ActionMode is implemented in the MainActivity.]]>
Async Filter
Big list with Asynchronous filter and refresh + Synchronization Animations + Configuration
Item Animators
ItemAnimators coherent with ScrollingAnimation
+ ItemAnimator coherent with ScrollingAnimation
+ AnimatedViewHolder is implemented.
+Index of section items is also provided to give a cascade effect when expanding/collapsing items.
+ScrollingAnimation gives an unique touch of animations when filling the RecyclerView or scrolling it.]]>
Headers and Sections
- Clickable sticky headers for sections + Draggable items with Auto-Linkage + Filter
+ Clickable sticky headers for sections + Draggable items with Auto-Linkage + Filter + Scrollable Headers and Footers
+ IHeader)]]>
+
+Sticky header is a real view and it\'s fully clickable, elevation and transparency are supported too.
+Using filter will disable the sticky header.
+Follow the steps in the Wiki page of the library.]]>
Expandable Sections
Sections with [sticky] headers that can expand/collapse + Draggable items + Filter + Scroll Animations
@@ -104,28 +136,35 @@ items and higher, due to calculate the correct position for each item to shift.
2 levels of expandable + Selection Coherence + Swipeable & Draggable items + ActionModeHelper + UndoHelper
Endless Scrolling
- EndlessScrolling + Drag + Swipe-To-Delete w/rear-views + Scroll Animations + ActionModeHelper + UndoHelper
+ EndlessScrolling + Drag + Swipe-To-Delete w/rear-views + Scroll Animations + ActionModeHelper + UndoHelper + Scrollable Headers and Footers
Instagram headers
Internet is required.]]>
Item Model Holder
Check the code!]]>
+ IHolder item interface]]>
+
+Use can use IHolder interface when you have to serialize/deserialize the model object coming from
+a network or from a different layer. Check the code!]]>
Horizontal Layout
Items are disposed in vertical
- Cards with sticky headers disposed in Staggered Layout, dynamic background, insertion and sorting
+ Cards with Sticky Headers disposed in Staggered Layout, dynamic background, insertion and sorting
+ Support for Staggered Layout
+ calculatePositionFor().
+Example of dynamic background with ripple effect without XML configuration.]]>
View Pager
- Example usage in ViewPager layout
+ Example usage with Sticky Headers in ViewPager layout
Dialogs
Example usage in Dialogs
Flexible Adapter
- GitHub page
+ GitHub page…
About
%1$s #%2$s
Github Profile
Licence
-Copyright © 2015-2016 Davide Steduto.
+Copyright © 2015-2017 Davide Steduto.
Licensed under the Apache License, Version 2.0 (the "License");
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -152,5 +191,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]>
+ ViewPagerActivity
+ Hello World from section: %1$d
\ No newline at end of file
diff --git a/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/FilterTest.java b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/FilterTest.java
new file mode 100644
index 00000000..941dbdca
--- /dev/null
+++ b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/FilterTest.java
@@ -0,0 +1,44 @@
+package eu.davidea.flexibleadapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+import eu.davidea.samples.flexibleadapter.services.DatabaseService;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Davide Steduto
+ * @since 18/10/2016
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 23)
+public class FilterTest {
+
+ FlexibleAdapter mAdapter;
+ List mItems;
+
+ @Before
+ public void setUp() throws Exception {
+ DatabaseService.getInstance().createHeadersSectionsDatabase(30, 5);
+ mItems = DatabaseService.getInstance().getDatabaseList();
+ }
+
+ @Test
+ public void testNoDelayFilter() throws Exception {
+ mAdapter = new FlexibleAdapter<>(mItems);
+ mAdapter.showAllHeaders();
+ mAdapter.setSearchText("1");
+ System.out.println(mAdapter.getItemCount());
+ mAdapter.filterItems(DatabaseService.getInstance().getDatabaseList());
+ System.out.println(mAdapter.getItemCount());
+ assertEquals(16, mAdapter.getItemCount());
+ }
+
+}
\ No newline at end of file
diff --git a/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/HeadersSectionsTest.java b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/HeadersSectionsTest.java
index fd41f1ac..aead28ed 100644
--- a/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/HeadersSectionsTest.java
+++ b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/HeadersSectionsTest.java
@@ -3,7 +3,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.List;
@@ -20,7 +20,7 @@
* @author Davide Steduto
* @since 23/06/2016
*/
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class HeadersSectionsTest {
diff --git a/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/ItemComparatorTest.java b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/ItemComparatorTest.java
index d9912680..705e8978 100644
--- a/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/ItemComparatorTest.java
+++ b/flexible-adapter-app/src/test/java/eu/davidea/flexibleadapter/ItemComparatorTest.java
@@ -3,7 +3,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class ItemComparatorTest {
diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java
index 2c75eadd..63cadcca 100644
--- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java
+++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/AnimatorAdapter.java
@@ -21,12 +21,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.support.annotation.CallSuper;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseArray;
@@ -42,6 +40,8 @@
import eu.davidea.flexibleadapter.utils.Utils;
import eu.davidea.viewholders.FlexibleViewHolder;
+import static eu.davidea.flexibleadapter.utils.Utils.getClassName;
+
/**
* This class is responsible to animate items. Bounded items are animated initially and also
* when user starts to scroll the list.
@@ -54,11 +54,12 @@
* @see SelectableAdapter
* @since 10/01/2016 Created
*
30/01/2016 Class now extends {@link SelectableAdapter}
+ *
13/09/2016 {@link #animateView(RecyclerView.ViewHolder, int)} is now automatically called
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class AnimatorAdapter extends SelectableAdapter {
- protected static final String TAG = AnimatorAdapter.class.getSimpleName();
+ private static final String TAG = AnimatorAdapter.class.getSimpleName();
private Interpolator mInterpolator = new LinearInterpolator();
private AnimatorAdapterDataObserver mAnimatorNotifierObserver;
@@ -89,7 +90,7 @@ private enum AnimatorEnum {
private EnumSet animatorsUsed = EnumSet.noneOf(AnimatorEnum.class);
private boolean isReverseEnabled = false, shouldAnimate = false,
- onlyEntryAnimation = false, isFastScroll = false, animateFromObserver = false;
+ onlyEntryAnimation = false, animateFromObserver = false;
private long mInitialDelay = 0L,
mStepDelay = 100L,
@@ -104,10 +105,10 @@ private enum AnimatorEnum {
*
* @since 5.0.0-b1
*/
- public AnimatorAdapter(boolean stableIds) {
+ AnimatorAdapter(boolean stableIds) {
super();
- if (stableIds && DEBUG) Log.i(TAG, "Setting StableIds");
setHasStableIds(stableIds);
+ if (DEBUG) Log.i("FlexibleAdapter", "Initialized with StableIds=" + stableIds);
//Get notified when an item is changed (should skip animation)
mAnimatorNotifierObserver = new AnimatorAdapterDataObserver();
@@ -123,12 +124,12 @@ public AnimatorAdapter(boolean stableIds) {
* animate items, false to inform that the operation is complete
* @since 5.0.0-b6
*/
- void setAnimate(boolean animate) {
+ void setScrollAnimate(boolean animate) {
this.animateFromObserver = animate;
}
/**
- * Customize the initial delay for the first item animation.
+ * Sets the initial delay for the first item animation.
* Default value is {@code 0ms}.
*
* @param initialDelay any non negative delay
@@ -136,12 +137,13 @@ void setAnimate(boolean animate) {
* @since 5.0.0-b1
*/
public AnimatorAdapter setAnimationInitialDelay(long initialDelay) {
+ if (DEBUG) Log.i(TAG, "Set animationInitialDelay=" + initialDelay);
mInitialDelay = initialDelay;
return this;
}
/**
- * Customize the step delay between an animation and the next to be added to the initial delay.
+ * Sets the step delay between an animation and the next to be added to the initial delay.
* The delay is added on top of the previous delay.
* Default value is {@code 100ms}.
*
@@ -150,6 +152,7 @@ public AnimatorAdapter setAnimationInitialDelay(long initialDelay) {
* @since 5.0.0-b1
*/
public AnimatorAdapter setAnimationDelay(@IntRange(from = 0) long delay) {
+ if (DEBUG) Log.i(TAG, "Set animationDelay=" + delay);
mStepDelay = delay;
return this;
}
@@ -165,12 +168,13 @@ public AnimatorAdapter setAnimationDelay(@IntRange(from = 0) long delay) {
* since 5.0.0-b8
*/
public AnimatorAdapter setAnimationEntryStep(boolean entryStep) {
+ if (DEBUG) Log.i(TAG, "Set animationEntryStep=" + entryStep);
this.mEntryStep = entryStep;
return this;
}
/**
- * Customize the duration of the animation for ALL items.
+ * Sets the duration of the animation for ALL items.
* Default value is {@code 300ms}.
*
* @param duration any positive time
@@ -178,41 +182,46 @@ public AnimatorAdapter setAnimationEntryStep(boolean entryStep) {
* @since 5.0.0-b1
*/
public AnimatorAdapter setAnimationDuration(@IntRange(from = 1) long duration) {
+ if (DEBUG) Log.i(TAG, "Set animationDuration=" + duration);
mDuration = duration;
return this;
}
/**
- * Define a custom interpolator for ALL items.
+ * Sets a custom interpolator for ALL items.
* Default value is {@link LinearInterpolator}.
*
* @param interpolator any valid non null interpolator
* @return this AnimatorAdapter, so the call can be chained
*/
public AnimatorAdapter setAnimationInterpolator(@NonNull Interpolator interpolator) {
+ if (DEBUG) Log.i(TAG, "Set animationInterpolator=" + getClassName(interpolator));
mInterpolator = interpolator;
return this;
}
/**
- * Define an initial start animation adapter position.
+ * Sets an initial start animation adapter position.
* Default value is {@code 0} (1st position).
*
* @param start non negative minimum position to start animation.
* @since 5.0.0-b1
+ * @deprecated Can't be supported anymore due to the new internal condition of non-animations.
*/
+ @Deprecated
public AnimatorAdapter setAnimationStartPosition(@IntRange(from = 0) int start) {
+ if (DEBUG) Log.i(TAG, "Set animationStartPosition=" + start);
mLastAnimatedPosition = start;
return this;
}
/**
- * Enable/Disable item animation while scrolling and on loading.
+ * Enables/Disables item animation while scrolling and on loading.
* Enabling scrolling will disable onlyEntryAnimation.
- * Disabling scrolling will disable also reverse scrolling!
+ * Disabling scrolling will disable also reverse scrolling.
* Default value is {@code false}.
- * Note: Loading animation can only be performed if the Adapter is initialized
- * with some items using the constructor.
+ * Note: Loading animation can only be performed if the Adapter is initialized
+ * with some items using the constructor.
*
* @param enabled true to enable item animation, false to disable them all.
* @return this AnimatorAdapter, so the call can be chained
@@ -221,6 +230,7 @@ public AnimatorAdapter setAnimationStartPosition(@IntRange(from = 0) int start)
* @since 5.0.0-b1
*/
public AnimatorAdapter setAnimationOnScrolling(boolean enabled) {
+ if (DEBUG) Log.i(TAG, "Set animationOnScrolling=" + enabled);
if (enabled) this.onlyEntryAnimation = false;
shouldAnimate = enabled;
return this;
@@ -231,7 +241,7 @@ public boolean isAnimationOnScrollingEnabled() {
}
/**
- * Enable reverse scrolling animation if AnimationOnScrolling is also enabled!
+ * Enables reverse scrolling animation if AnimationOnScrolling is also enabled!
* Value is ignored if basic animation on scrolling is disabled.
* Default value is {@code false} (only forward).
*
@@ -241,6 +251,7 @@ public boolean isAnimationOnScrollingEnabled() {
* @since 5.0.0-b1
*/
public AnimatorAdapter setAnimationOnReverseScrolling(boolean enabled) {
+ if (DEBUG) Log.i(TAG, "Set animationOnReverseScrolling=" + enabled);
isReverseEnabled = enabled;
return this;
}
@@ -248,11 +259,21 @@ public AnimatorAdapter setAnimationOnReverseScrolling(boolean enabled) {
/**
* @return true if items are animated also on reverse scrolling, false only forward
* @since 5.0.0-b1
+ * @deprecated use {@link #isAnimationOnReverseScrollingEnabled()}
*/
+ @Deprecated
public boolean isAnimationOnReverseScrolling() {
return isReverseEnabled;
}
+ /**
+ * @return true if items are animated also on reverse scrolling, false only forward
+ * @since 5.0.0-b1
+ */
+ public boolean isAnimationOnReverseScrollingEnabled() {
+ return isReverseEnabled;
+ }
+
/**
* Performs only entry animation during the initial loading. Stops the animation after
* the last visible item in the RecyclerView has been animated.
@@ -266,6 +287,7 @@ public boolean isAnimationOnReverseScrolling() {
* @since 5.0.0-b8
*/
public AnimatorAdapter setOnlyEntryAnimation(boolean enabled) {
+ if (DEBUG) Log.i(TAG, "Set onlyEntryAnimation=" + enabled);
if (enabled) this.shouldAnimate = true;
this.onlyEntryAnimation = enabled;
return this;
@@ -280,34 +302,10 @@ public boolean isOnlyEntryAnimation() {
return onlyEntryAnimation;
}
- /**
- * Triggered by the FastScroller when handle is touched
- *
- * @param scrolling boolean to indicate that the handle is being fast scrolled
- * @since 5.0.0-b1
- */
- @Override
- public void onFastScrollerStateChange(boolean scrolling) {
- super.onFastScrollerStateChange(scrolling);
- isFastScroll = scrolling;
- }
-
/*--------------*/
/* MAIN METHODS */
/*--------------*/
- @Override
- @CallSuper
- public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
- int position = holder.getAdapterPosition();
-// if (DEBUG) {
-// Log.v(TAG, "onViewAttached Holder=" + holder.getClass().getSimpleName() +
-// " position=" + position +
-// " itemId=" + holder.getItemId());
-// }
- animateView(holder, position);
- }
-
/**
* Build your custom list of {@link Animator} to apply on the ItemView.
* Write the logic based on the position and/or viewType and/or the item selection.
@@ -339,52 +337,83 @@ private void cancelExistingAnimation(final int hashCode) {
if (animator != null) animator.end();
}
- protected void animateView(final RecyclerView.ViewHolder holder, final int position) {
- //FIXME: first completed visible item on rotation gets high delay
+ /**
+ * Checks if at the provided position, the item is a Header or Footer
+ *
+ * @param position the position to check
+ * @return true if is a scrollable item
+ */
+ public abstract boolean isScrollableHeaderOrFooter(int position);
-// if (DEBUG)
+ /**
+ * Performs checks to scroll animate the itemView and in case, it animates the view.
+ * Note: If you have to change at runtime the LayoutManager and add
+ * Scrollable Headers too, consider to add them in post, using a {@code delay >= 0},
+ * otherwise scroll animations on all items will not start correctly.
+ *
+ * @param holder the ViewHolder just bound
+ * @param position the current item position
+ */
+ @SuppressWarnings("ConstantConditions")
+ protected final void animateView(final RecyclerView.ViewHolder holder, final int position) {
+ if (mRecyclerView == null) return;
+
+ // Use always the max child count reached
+ if (mMaxChildViews < mRecyclerView.getChildCount()) {
+ mMaxChildViews = mRecyclerView.getChildCount();
+ }
+ // Animate only during initial loading?
+ if (onlyEntryAnimation && mLastAnimatedPosition >= mMaxChildViews) {
+ shouldAnimate = false;
+ }
+ int lastVisiblePosition = Utils.findLastVisibleItemPosition(mRecyclerView.getLayoutManager());
+// if (DEBUG) {
// Log.v(TAG, "shouldAnimate=" + shouldAnimate
// + " isFastScroll=" + isFastScroll
// + " isNotified=" + mAnimatorNotifierObserver.isPositionNotified()
// + " isReverseEnabled=" + isReverseEnabled
// + " mLastAnimatedPosition=" + mLastAnimatedPosition
-// + (!isReverseEnabled ? " Pos>AniPos=" + (position > mLastAnimatedPosition) : "")
+// + (!isReverseEnabled ? " Pos>LasVisPos=" + (position > lastVisiblePosition) : "")
+// + " mMaxChildViews=" + mMaxChildViews
// );
-
+// }
if (holder instanceof FlexibleViewHolder && shouldAnimate && !isFastScroll &&
!mAnimatorNotifierObserver.isPositionNotified() &&
- (isReverseEnabled || position > mLastAnimatedPosition || (position == 0 && mRecyclerView.getChildCount() == 0)) ) {
+ (position > lastVisiblePosition || isReverseEnabled || isScrollableHeaderOrFooter(position) || (position == 0 && mMaxChildViews == 0))) {
- //Cancel animation is necessary when fling
+ // Cancel animation is necessary when fling
int hashCode = holder.itemView.hashCode();
cancelExistingAnimation(hashCode);
- //User animators
+ // User animators
List animators = new ArrayList<>();
FlexibleViewHolder flexibleViewHolder = (FlexibleViewHolder) holder;
- flexibleViewHolder.scrollAnimators(animators, position, position > mLastAnimatedPosition);
+ flexibleViewHolder.scrollAnimators(animators, position, position >= lastVisiblePosition);
- //Execute the animations together
+ // Execute the animations together
AnimatorSet set = new AnimatorSet();
set.playTogether(animators);
set.setInterpolator(mInterpolator);
- set.setDuration(mDuration);
+ // Single view duration
+ long duration = 0L;
+ for (Animator animator : animators) {
+ if (animator.getDuration() != mDuration) {
+ duration = animator.getDuration();
+ }
+ }
+ //Log.v(TAG, "duration=" + duration);
+ set.setDuration(duration > 0 ? duration : mDuration);
set.addListener(new HelperAnimatorListener(hashCode));
if (mEntryStep) {
- //Stop stepDelay when screen is filled
- set.setStartDelay(calculateAnimationDelay2(position));
+ // Stop stepDelay when screen is filled
+ set.setStartDelay(calculateAnimationDelay(position));
}
set.start();
mAnimators.put(hashCode, set);
- if (DEBUG) Log.d(TAG, "Started Animation on position " + position);
-
- //Animate only during initial loading?
- if (onlyEntryAnimation && position >= mMaxChildViews) {
- shouldAnimate = false;
- }
+ if (DEBUG) Log.v(TAG, "animateView Scroll animation on position " + position);
}
-
mAnimatorNotifierObserver.clearNotified();
+ // Update last animated position
mLastAnimatedPosition = position;
}
@@ -418,7 +447,7 @@ public final void animateView(final View itemView, int position) {
//Add Alpha animator
ViewCompat.setAlpha(itemView, 0);
animators.add(ObjectAnimator.ofFloat(itemView, "alpha", 0f, 1f));
- if (DEBUG) Log.d(TAG, "Started Deprecated Animation on position " + position);
+ Log.w(TAG, "Started Deprecated Animation on position " + position);
//Execute the animations
AnimatorSet set = new AnimatorSet();
@@ -427,8 +456,7 @@ public final void animateView(final View itemView, int position) {
set.setDuration(mDuration);
set.addListener(new HelperAnimatorListener(itemView.hashCode()));
if (mEntryStep) {
- //set.setStartDelay(calculateAnimationDelay1(position));
- set.setStartDelay(calculateAnimationDelay2(position));
+ set.setStartDelay(calculateAnimationDelay(position));
}
set.start();
mAnimators.put(itemView.hashCode(), set);
@@ -444,87 +472,55 @@ public final void animateView(final View itemView, int position) {
}
/**
- * Solution 1.
- * Reset stepDelay.
+ * @param position the position just bound
+ * @return the delay in milliseconds after which, the animation for next ItemView should start.
*/
- private long calculateAnimationDelay1(int position) {
+ private long calculateAnimationDelay(int position) {
+ long delay;
int firstVisiblePosition = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
int lastVisiblePosition = Utils.findLastCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
- //Use always the max child count reached
- if (mMaxChildViews < mRecyclerView.getChildCount())
- mMaxChildViews = mRecyclerView.getChildCount();
+ // Fix for high delay on the first visible item on rotation
+ if (firstVisiblePosition < 0 && position >= 0)
+ firstVisiblePosition = position - 1;
- if (mLastAnimatedPosition > lastVisiblePosition)
- lastVisiblePosition = mLastAnimatedPosition;
+ // Last visible position is the last animated when initially loading
+ if (position - 1 > lastVisiblePosition)
+ lastVisiblePosition = position - 1;
int visibleItems = lastVisiblePosition - firstVisiblePosition;
-
-// if (DEBUG) Log.v(TAG, "Position=" + position +
-// " FirstVisible=" + firstVisiblePosition +
-// " LastVisible=" + lastVisiblePosition +
-// " LastAnimated=" + mLastAnimatedPosition +
-// " VisibleItems=" + visibleItems +
-// " ChildCount=" + mRecyclerView.getChildCount());
-
- //Stop stepDelay when screen is filled
- if (mLastAnimatedPosition > visibleItems || //Normal Forward scrolling
- (firstVisiblePosition > 1 && firstVisiblePosition <= mMaxChildViews)) { //Reverse scrolling
- if (DEBUG) Log.v(TAG, "Reset AnimationDelay on position " + position);
- return 0L;
- }
-
- return mInitialDelay += mStepDelay;
- }
-
- /**
- * Solution 2.
- * Returns the delay in milliseconds after which, the animation for next ItemView should start.
- */
- private long calculateAnimationDelay2(int position) {
- long delay;
- int firstVisiblePosition = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
- int lastVisiblePosition = Utils.findLastCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
-
- if (mLastAnimatedPosition > lastVisiblePosition)
- lastVisiblePosition = mLastAnimatedPosition;
-
- int numberOfItemsOnScreen = lastVisiblePosition - firstVisiblePosition;
int numberOfAnimatedItems = position - 1;
- //Save max child count reached
- if (mMaxChildViews < mRecyclerView.getChildCount())
- mMaxChildViews = mRecyclerView.getChildCount();
-
- if (numberOfItemsOnScreen == 0 || numberOfItemsOnScreen < numberOfAnimatedItems || //Normal Forward scrolling after max itemOnScreen is reached
+ if (mMaxChildViews == 0 || visibleItems < numberOfAnimatedItems || //Normal Forward scrolling after max itemOnScreen is reached
(firstVisiblePosition > 1 && firstVisiblePosition <= mMaxChildViews) || //Reverse scrolling
(position > mMaxChildViews && firstVisiblePosition == -1 && mRecyclerView.getChildCount() == 0)) { //Reverse scrolling and click on FastScroller
- //Base delay is step delay
+ // Base delay is step delay
delay = mStepDelay;
- if (numberOfItemsOnScreen <= 1) {
- //When RecyclerView is initially loading no items are present
- //Use InitialDelay only for the first item
+ if (visibleItems <= 1) {
+ // When RecyclerView is initially loading no items are present
+ // Use InitialDelay only for the first item
delay += mInitialDelay;
} else {
- //Reset InitialDelay only when first item is already animated
+ // Reset InitialDelay only when first item is already animated
mInitialDelay = 0L;
}
- if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) {
- int numColumns = ((GridLayoutManager) mRecyclerView.getLayoutManager()).getSpanCount();
+ int numColumns = Utils.getSpanCount(mRecyclerView.getLayoutManager());
+ if (numColumns > 1) {
delay = mInitialDelay + mStepDelay * (position % numColumns);
}
- } else {//forward scrolling before max itemOnScreen is reached
+ } else { //forward scrolling before max itemOnScreen is reached
delay = mInitialDelay + (position * mStepDelay);
}
// if (DEBUG) Log.v(TAG, "Delay[" + position + "]=" + delay +
// " FirstVisible=" + firstVisiblePosition +
// " LastVisible=" + lastVisiblePosition +
-// " LastAnimated=" + mLastAnimatedPosition +
-// " VisibleItems=" + numberOfItemsOnScreen +
-// " ChildCount=" + mRecyclerView.getChildCount());
+// " LastAnimated=" + numberOfAnimatedItems +
+// " VisibleItems=" + visibleItems +
+// " ChildCount=" + mRecyclerView.getChildCount() +
+// " MaxChildCount=" + mMaxChildViews);
return delay;
}
@@ -671,13 +667,13 @@ public void addScaleInAnimator(
/**
* Observer Class responsible to skip animation when items are notified to avoid
* double animation with {@link android.support.v7.widget.RecyclerView.ItemAnimator}.
- * Also, some items at the edge, are rebounded by Android and should not be animated.
+ * Also, some items at the edge, are rebound by Android and should not be animated.
*/
private class AnimatorAdapterDataObserver extends RecyclerView.AdapterDataObserver {
private boolean notified;
private Handler mAnimatorHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
public boolean handleMessage(Message message) {
- if (DEBUG) Log.v(TAG, "Clear notified for binding Animations");
+ if (DEBUG) Log.v(TAG, "Clear notified for scrolling Animations");
notified = false;
return true;
}
@@ -696,6 +692,8 @@ public void clearNotified() {
private void markNotified() {
notified = !animateFromObserver;
+// if (DEBUG)
+// Log.v(TAG, "animateFromObserver=" + animateFromObserver + " notified=" + notified);
}
@Override
@@ -737,7 +735,6 @@ private class HelperAnimatorListener implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {
-
}
@Override
diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java
index 63022538..77a798df 100644
--- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java
+++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java
@@ -22,10 +22,11 @@
import android.os.Looper;
import android.os.Message;
import android.support.annotation.CallSuper;
+import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v4.view.ViewCompat;
+import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
@@ -43,6 +44,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import eu.davidea.flexibleadapter.common.SmoothScrollGridLayoutManager;
@@ -58,20 +60,21 @@
import eu.davidea.viewholders.ExpandableViewHolder;
import eu.davidea.viewholders.FlexibleViewHolder;
+import static eu.davidea.flexibleadapter.utils.Utils.getClassName;
+
/**
- * This class is backed by an ArrayList of arbitrary objects of T, where T is
- * your Model object containing the data, with version 5.0.0 it must implement {@link IFlexible}
- * interface. Read Wiki page on
- * Github for more details.
- * This class provides a set of standard methods to handle changes on the data set such as
- * filtering, adding, removing, moving and animating an item.
- * With version 5.0.0, this Adapter supports a set of standard methods for Headers/Sections to
- * expand and collapse an Expandable item, to Drag&Drop and Swipe any item.
- * NOTE: This Adapter supports multi level of Expandable, but do not enable Drag&Drop.
- * Something might not work as expected, so better to change approach in favor of a clearer
- * design/layout: Open the sub list in a new Activity/Fragment...
- *
Instead, this extra level of expansion is useful in situations where information is in
- * read only mode or with action buttons.
+ * This Adapter is backed by an ArrayList of arbitrary objects of class T, where T
+ * is your adapter/model object containing the data of a single item. This Adapter simplifies the
+ * development by providing a set of standard methods to handle changes on the data set such as:
+ * selecting, filtering, adding, removing, moving and animating an item.
+ *
+ * With version 5.0.0, T item must implement {@link IFlexible} item interface as base item
+ * for all view types and new methods have been added in order to:
+ *
+ * - handle Headers/Sections with {@link IHeader} and {@link ISectionable} items;
+ * - expand and collapse {@link IExpandable} items;
+ * - drag&drop and swipe any items.
+ *
*
* @author Davide Steduto
* @see AnimatorAdapter
@@ -88,7 +91,8 @@
*
10/02/2016 The class is not abstract anymore, it is ready to be used
*
20/02/2016 Sticky headers
*
22/04/2016 Endless Scrolling
- *
09/07/2016 FilterAsyncTask (performance on big list)
+ *
13/07/2016 Update and Filter operations are executed asynchronously (high performance on big list)
+ *
25/11/2016 Scrollable Headers and Footers; Reviewed EndlessScroll
*/
@SuppressWarnings({"Range", "unused", "unchecked", "ConstantConditions", "SuspiciousMethodCalls", "WeakerAccess"})
public class FlexibleAdapter
@@ -99,73 +103,41 @@ public class FlexibleAdapter
private static final String EXTRA_PARENT = TAG + "_parentSelected";
private static final String EXTRA_CHILD = TAG + "_childSelected";
private static final String EXTRA_HEADERS = TAG + "_headersShown";
+ private static final String EXTRA_STICKY = TAG + "_stickyHeaders";
private static final String EXTRA_LEVEL = TAG + "_selectedLevel";
private static final String EXTRA_SEARCH = TAG + "_searchText";
- /**
- * Handler operations
- */
- private static final int
- UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2,
- LOAD_MORE_COMPLETE = 8, LOAD_MORE_RESET = 9;
-
- /**
- * The main container for ALL items.
- */
+ /* The main container for ALL items */
private List mItems, mTempItems;
- /**
- * HashSet and AsyncTask objects, will increase performance in big list
- */
- private Set hashItems;
- private List notifications;
+ /* HashSet, AsyncTask and DiffUtil objects, will increase performance in big list */
+ private Set mHashItems;
+ private List mNotifications;
private FilterAsyncTask mFilterAsyncTask;
+ private long start, time;
+ private boolean useDiffUtil = false;
+ private DiffUtil.DiffResult diffResult;
+ private DiffUtilCallback diffUtilCallback;
- /**
- * Handler for delayed actions.
- * You can use and override this Handler, but you must keep the "What" by calling super():
- *
0 = updateDataSet.
- *
1 = filterItems, optionally delayed.
- *
2 = deleteConfirmed when Undo timeout is over.
- *
8 = remove the progress item from the list, optionally delayed.
- *
9 = reset flag to load more items, delayed.
- * Note: numbers 0-9 are reserved for the Adapter, use others.
- */
- protected Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case UPDATE: //updateDataSet OR
- case FILTER: //filterItems
- if (mFilterAsyncTask != null) mFilterAsyncTask.cancel(true);
- mFilterAsyncTask = new FilterAsyncTask(message.what, (List) message.obj);
- mFilterAsyncTask.execute();
- return true;
- case CONFIRM_DELETE: //confirm delete
- OnDeleteCompleteListener listener = (OnDeleteCompleteListener) message.obj;
- if (listener != null) listener.onDeleteConfirmed();
- emptyBin();
- return true;
- case LOAD_MORE_COMPLETE: //onLoadMore remove progress item
- deleteProgressItem();
- return true;
- case LOAD_MORE_RESET: //onLoadMore reset
- resetOnLoadMore();
- return true;
- }
- return false;
- }
- });
+ /* Handler for delayed actions */
+ protected final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8;
+ protected Handler mHandler = new Handler(Looper.getMainLooper(), new HandlerCallback());
- /* Used to save deleted items and to recover them (Undo) */
+ /* Deleted items and RestoreList (Undo) */
public static final long UNDO_TIMEOUT = 5000L;
private List mRestoreList;
private boolean restoreSelection = false, multiRange = false, unlinkOnRemoveHeader = false,
removeOrphanHeaders = false, permanentDelete = true, adjustSelected = true;
- /* Header/Section items */
+ /* Scrollable Headers/Footers items */
+ private List mScrollableHeaders, mScrollableFooters;
+
+ /* Section items (with sticky headers) */
private List mOrphanHeaders;
- private boolean headersShown = false, headersSticky = false, recursive = false;
+ private boolean headersShown = false, recursive = false;
+ private float mStickyElevation;
private StickyHeaderHelper mStickyHeaderHelper;
+ private ViewGroup mStickyContainer;
/* ViewTypes */
protected LayoutInflater mInflater;
@@ -178,21 +150,21 @@ public boolean handleMessage(Message message) {
private Set mExpandedFilterFlags;
private boolean notifyChangeOfUnfilteredItems = false, filtering = false,
notifyMoveOfFilteredItems = false;
- private static int mAnimateToLimit = 600;
+ private static int ANIMATE_TO_LIMIT = 700;
+ private int mAnimateToLimit = ANIMATE_TO_LIMIT;
/* Expandable flags */
- private int minCollapsibleLevel = 0, selectedLevel = -1;
+ private int mMinCollapsibleLevel = 0, mSelectedLevel = -1;
private boolean scrollOnExpand = false, collapseOnExpand = false,
childSelected = false, parentSelected = false;
/* Drag&Drop and Swipe helpers */
- private boolean handleDragEnabled = false;
private ItemTouchHelperCallback mItemTouchHelperCallback;
private ItemTouchHelper mItemTouchHelper;
/* EndlessScroll */
- private int mEndlessScrollThreshold = 1;
- private boolean mLoading = false;
+ private int mEndlessScrollThreshold = 1, mEndlessTargetCount = 0, mEndlessPageSize = 0;
+ private boolean endlessLoading = false, endlessScrollEnabled = false;
private T mProgressItem;
/* Listeners */
@@ -212,6 +184,8 @@ public boolean handleMessage(Message message) {
* Simple Constructor with NO listeners!
*
* @param items items to display.
+ * @see #FlexibleAdapter(List, Object)
+ * @see #FlexibleAdapter(List, Object, boolean)
* @since 4.2.0
*/
public FlexibleAdapter(@Nullable List items) {
@@ -220,21 +194,24 @@ public FlexibleAdapter(@Nullable List items) {
/**
* Main Constructor with all managed listeners for ViewHolder and the Adapter itself.
- * The listener must be a single instance of a class, usually Activity or Fragment,
- * where you can implement how to handle the different events.
- * Any write operation performed on the items list is synchronized.
- * PASS ALWAYS A COPY OF THE ORIGINAL LIST: new ArrayList<T>(originalList);
+ * The listener must be a single instance of a class, usually Activity or
+ * Fragment, where you can implement how to handle the different events.
+ * PROVIDE ALWAYS A COPY OF THE ORIGINAL LIST:
+ * {@code new ArrayList(originalList);}
*
* @param items items to display
* @param listeners can be an instance of:
*
- * - {@link OnUpdateListener}
*
- {@link OnItemClickListener}
*
- {@link OnItemLongClickListener}
*
- {@link OnItemMoveListener}
*
- {@link OnItemSwipeListener}
*
- {@link OnStickyHeaderChangeListener}
+ *
- {@link OnUpdateListener}
*
+ * @see #FlexibleAdapter(List)
+ * @see #FlexibleAdapter(List, Object, boolean)
+ * @see #addListener(Object)
* @since 5.0.0-b1
*/
public FlexibleAdapter(@Nullable List items, @Nullable Object listeners) {
@@ -244,25 +221,30 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners) {
/**
* Same as {@link #FlexibleAdapter(List, Object)} with possibility to set stableIds.
* Note: Setting true allows the RecyclerView to rebind only items really changed
- * after a refresh or after swapping Adapter. This increase performance, you loose scrolling
- * animations.
- * Set {@code true} if items implements {@code hashcode()} and have stable ids. The method
+ * after a refresh or after swapping Adapter. This increase performance, but you loose
+ * scrolling animations.
+ * Set {@code true} only if items implement {@code hashcode()} and have stable ids. The method
* {@link #setHasStableIds(boolean)} will be called.
*
- * @param stableIds set {@code true} if items implements {@code hashcode()} and have stable ids.
+ * @param stableIds set {@code true} if item implements {@code hashcode()} and have stable ids.
+ * @see #FlexibleAdapter(List)
+ * @see #FlexibleAdapter(List, Object)
+ * @see #addListener(Object)
* @since 5.0.0-b8
*/
public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, boolean stableIds) {
super(stableIds);
if (items == null) mItems = new ArrayList<>();
else mItems = items;
+ mScrollableHeaders = new ArrayList<>();
+ mScrollableFooters = new ArrayList<>();
mRestoreList = new ArrayList<>();
mOrphanHeaders = new ArrayList<>();
- //Create listeners instances
- initializeListeners(listeners);
+ // Create listeners instances
+ addListener(listeners);
- //Get notified when items are inserted or removed (it adjusts selected positions)
+ // Get notified when items are inserted or removed (it adjusts selected positions)
registerAdapterDataObserver(new AdapterDataObserver());
}
@@ -270,31 +252,59 @@ public FlexibleAdapter(@Nullable List items, @Nullable Object listeners, bool
* Initializes the listener(s) of this Adapter.
* This method is automatically called from the Constructor.
*
- * @param listeners the object(s) instance(s) of any listener
+ * @param listener the object(s) instance(s) of any listener
+ * @return this Adapter, so the call can be chained
+ * @deprecated Use {@link #addListener(Object)}
+ */
+ @Deprecated
+ public FlexibleAdapter initializeListeners(@Nullable Object listener) {
+ return addListener(listener);
+ }
+
+ /**
+ * Initializes the listener(s) of this Adapter.
+ * This method is automatically called from the Constructor.
+ *
+ * @param listener the object(s) instance(s) of any listener
* @return this Adapter, so the call can be chained
* @since 5.0.0-b6
*/
- public FlexibleAdapter initializeListeners(@Nullable Object listeners) {
- if (listeners instanceof OnUpdateListener) {
- mUpdateListener = (OnUpdateListener) listeners;
- mUpdateListener.onUpdateEmptyView(getItemCount());
- }
- if (listeners instanceof OnItemClickListener)
- mItemClickListener = (OnItemClickListener) listeners;
- if (listeners instanceof OnItemLongClickListener)
- mItemLongClickListener = (OnItemLongClickListener) listeners;
- if (listeners instanceof OnItemMoveListener)
- mItemMoveListener = (OnItemMoveListener) listeners;
- if (listeners instanceof OnItemSwipeListener)
- mItemSwipeListener = (OnItemSwipeListener) listeners;
- if (listeners instanceof OnStickyHeaderChangeListener)
- mStickyHeaderChangeListener = (OnStickyHeaderChangeListener) listeners;
+ @CallSuper
+ public FlexibleAdapter addListener(@Nullable Object listener) {
+ if (DEBUG && listener != null) {
+ Log.i(TAG, "Adding listener class " + getClassName(listener) + " as:");
+ }
+ if (listener instanceof OnItemClickListener) {
+ if (DEBUG) Log.i(TAG, "- OnItemClickListener");
+ mItemClickListener = (OnItemClickListener) listener;
+ }
+ if (listener instanceof OnItemLongClickListener) {
+ if (DEBUG) Log.i(TAG, "- OnItemLongClickListener");
+ mItemLongClickListener = (OnItemLongClickListener) listener;
+ }
+ if (listener instanceof OnItemMoveListener) {
+ if (DEBUG) Log.i(TAG, "- OnItemMoveListener");
+ mItemMoveListener = (OnItemMoveListener) listener;
+ }
+ if (listener instanceof OnItemSwipeListener) {
+ if (DEBUG) Log.i(TAG, "- OnItemSwipeListener");
+ mItemSwipeListener = (OnItemSwipeListener) listener;
+ }
+ if (listener instanceof OnStickyHeaderChangeListener) {
+ if (DEBUG) Log.i(TAG, "- OnStickyHeaderChangeListener");
+ mStickyHeaderChangeListener = (OnStickyHeaderChangeListener) listener;
+ }
+ if (listener instanceof OnUpdateListener) {
+ if (DEBUG) Log.i(TAG, "- OnUpdateListener");
+ mUpdateListener = (OnUpdateListener) listener;
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
+ }
return this;
}
/**
* {@inheritDoc}
- * Attaches the StickyHeaderHelper from the RecyclerView when necessary
+ * Attaches the StickyHeaderHelper from the RecyclerView if necessary.
*
* @since 5.0.0-b6
*/
@@ -315,7 +325,7 @@ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
if (mStickyHeaderHelper != null) {
- mStickyHeaderHelper.detachFromRecyclerView(mRecyclerView);
+ mStickyHeaderHelper.detachFromRecyclerView();
mStickyHeaderHelper = null;
}
super.onDetachedFromRecyclerView(recyclerView);
@@ -325,17 +335,16 @@ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
* Maps and expands items that are initially configured to be shown as expanded.
* This method should be called during the creation of the Activity/Fragment, useful also
* after a screen rotation.
- *
It is also called after DataSet is updated.
- * Note: Only items at level 0 are automatically expanded, ignored all sub-levels.
+ *
It is also called after the data set is updated.
*
* @return this Adapter, so the call can be chained
* @since 5.0.0-b6
*/
public FlexibleAdapter expandItemsAtStartUp() {
int position = 0;
- setAnimate(true);
+ setScrollAnimate(true);
multiRange = true;
- while (position < mItems.size()) {
+ while (position < getItemCount()) {
T item = getItem(position);
if (isExpanded(item)) {
expand(position, false, true);
@@ -345,7 +354,7 @@ public FlexibleAdapter expandItemsAtStartUp() {
position++;
}
multiRange = false;
- setAnimate(false);
+ setScrollAnimate(false);
return this;
}
@@ -355,6 +364,7 @@ public FlexibleAdapter expandItemsAtStartUp() {
/**
* Checks if the current item has the property {@code enabled = true}.
+ * When an item is disabled, user cannot interact with it.
*
* @param position the current position of the item to check
* @return true if the item property enabled is set true, false otherwise
@@ -379,33 +389,33 @@ public boolean isSelectable(int position) {
/**
* {@inheritDoc}
*
- * @param position Position of the item to toggle the selection status for.
+ * @param position position of the item to toggle the selection status for.
* @since 5.0.0-b1
*/
+ //TODO: Review the logic of selection coherence
@Override
public void toggleSelection(@IntRange(from = 0) int position) {
T item = getItem(position);
- //Allow selection only for selectable items
+ // Allow selection only for selectable items
if (item != null && item.isSelectable()) {
IExpandable parent = getExpandableOf(item);
boolean hasParent = parent != null;
if ((isExpandable(item) || !hasParent) && !childSelected) {
- //Allow selection of Parent if no Child has been previously selected
+ // Allow selection of Parent if no Child has been previously selected
parentSelected = true;
- if (hasParent) selectedLevel = parent.getExpansionLevel();
+ if (hasParent) mSelectedLevel = parent.getExpansionLevel();
super.toggleSelection(position);
- } else if (!parentSelected && hasParent && parent.getExpansionLevel() + 1 == selectedLevel
- || selectedLevel == -1) {
- //Allow selection of Child of same level and if no Parent has been previously selected
+ } else if (!parentSelected && hasParent && parent.getExpansionLevel() + 1 == mSelectedLevel
+ || mSelectedLevel == -1) {
+ // Allow selection of Child of same level and if no Parent has been previously selected
childSelected = true;
- selectedLevel = parent.getExpansionLevel() + 1;
+ mSelectedLevel = parent.getExpansionLevel() + 1;
super.toggleSelection(position);
}
}
-
- //Reset flags if necessary, just to be sure
+ // Reset flags if necessary, just to be sure
if (getSelectedItemCount() == 0) {
- selectedLevel = -1;
+ mSelectedLevel = -1;
parentSelected = childSelected = false;
}
}
@@ -416,21 +426,21 @@ public void toggleSelection(@IntRange(from = 0) int position) {
* Examples:
*
- if user initially selects an expandable of type A, then only expandable items of
* type A will be selected.
- *
- if user initially selects a non-expandable of type B, then only items of Type B
+ *
- if user initially selects a non-expandable of type B, then only items of type B
* will be selected.
*
- The developer can override this behaviour by passing a list of viewTypes for which
* he wants to force the selection.
*
- * @param viewTypes All the desired viewTypes to be selected, pass nothing to automatically
- * select all the viewTypes of the first item user selected
+ * @param viewTypes All the desired viewTypes to be selected, providing no view types, will
+ * automatically select all the viewTypes of the first item user has selected
* @since 5.0.0-b1
*/
@Override
public void selectAll(Integer... viewTypes) {
if (getSelectedItemCount() > 0 && viewTypes.length == 0) {
- super.selectAll(getItemViewType(getSelectedPositions().get(0)));//Priority on the first item
+ super.selectAll(getItemViewType(getSelectedPositions().get(0))); //Priority on the first item
} else {
- super.selectAll(viewTypes);//Force the selection for the viewTypes passed
+ super.selectAll(viewTypes); //Force the selection for the viewTypes passed
}
}
@@ -467,8 +477,10 @@ public boolean isAnyChildSelected() {
/*--------------*/
/**
- * Convenience method of {@link #updateDataSet(List, boolean)}.
- * In this case changes will NOT be animated: {@link #notifyDataSetChanged()} will be invoked.
+ * Convenience method of {@link #updateDataSet(List, boolean)} (You should read the comments
+ * of this method).
+ * In this call, changes will NOT be animated: Warning!
+ * {@link #notifyDataSetChanged()} will be invoked.
*
* @param items the new data set
* @see #updateDataSet(List, boolean)
@@ -480,47 +492,59 @@ public void updateDataSet(List items) {
}
/**
- * This method will refresh the entire DataSet content.
- * Optionally all changes can be animated, limited by the value previously set with
- * {@link #setAnimateToLimit(int)} to improve performance on big list.
- * Pass {@code animate = false} to directly invoke {@link #notifyDataSetChanged()}
- * without any animations.
- * Note: This methods calls {@link #expandItemsAtStartUp()} and
- * {@link #showAllHeaders()} if headers are shown.
+ * This method will refresh the entire data set content. Optionally, all changes can be
+ * animated, limited by the value previously set with {@link #setAnimateToLimit(int)}
+ * to improve performance on very big list. Should provide {@code animate=false} to
+ * directly invoke {@link #notifyDataSetChanged()} without any animations, if {@code stableIds}
+ * is not set!
+ * Note:
+ *
- Scrollable Headers and Footers (if existent) will be restored in this call.
+ * - I strongly recommend to implement {@link #hashCode()} to all adapter items along with
+ * {@link #equals(Object)}: This Adapter is making use of HashSet to improve performance.
+ *
+ * Note: The following methods will be also called at the end of the operation:
+ * - {@link #expandItemsAtStartUp()}
+ * - {@link #showAllHeaders()} if headers are shown
+ * - {@link #onPostUpdate()}
+ * - {@link OnUpdateListener#onUpdateEmptyView(int)} if the listener is set
*
* @param items the new data set
- * @param animate true to animate the changes, false for a quick refresh
+ * @param animate true to animate the changes, false for an instant refresh
* @see #updateDataSet(List)
* @see #setAnimateToLimit(int)
+ * @see #onPostUpdate()
* @since 5.0.0-b7 Created
*
5.0.0-b8 Synchronization animations limit
*/
@CallSuper
public void updateDataSet(@Nullable List items, boolean animate) {
+ if (items == null) items = new ArrayList<>();
+ restoreScrollableHeadersAndFooters(items);
if (animate) {
mHandler.removeMessages(UPDATE);
mHandler.sendMessage(Message.obtain(mHandler, UPDATE, items));
} else {
- if (items == null) mItems = new ArrayList<>();
- else mItems = new ArrayList<>(items);
+ mItems = items;
postUpdate(true);
}
}
/**
- * Returns the custom object "Item".
- * This cannot be overridden since the entire library relies on it.
+ * Returns the object of type T.
+ * This method cannot be overridden since the entire library relies on it.
*
* @param position the position of the item in the list
- * @return The custom "Item" object or null if item not found
+ * @return The T object for the position provided or null if item not found
* @since 1.0.0
*/
public final T getItem(@IntRange(from = 0) int position) {
- if (position < 0 || position >= mItems.size()) return null;
+ if (position < 0 || position >= getItemCount()) return null;
return mItems.get(position);
}
/**
+ * This method is mostly used by the adapter if items have stableIds.
+ *
* @param position the position of the current item
* @return Hashcode of the item at the specific position
* @since 5.0.0-b1
@@ -532,17 +556,39 @@ public long getItemId(int position) {
}
/**
- * This cannot be overridden since the selection relies on it.
+ * Returns the total number of items in the data set held by the adapter (headers and footers
+ * INCLUDED). Use {@link #getMainItemCount()} with {@code false} as parameter to retrieve
+ * only real items excluding headers and footers.
+ * Note: This method cannot be overridden since the selection and the internal
+ * methods rely on it.
*
- * @return the total number of the items currently displayed by the adapter
+ * @return the total number of items (headers and footers INCLUDED) held by the adapter
+ * @see #getMainItemCount()
* @see #getItemCountOfTypes(Integer...)
- * @see #getItemCountOfTypesUntil(int, Integer...)
* @see #isEmpty()
* @since 1.0.0
*/
@Override
public final int getItemCount() {
- return mItems != null ? mItems.size() : 0;
+ return mItems.size();
+ }
+
+ /**
+ * Returns the total number of items in the data set held by the adapter.
+ *
+ * - Provide {@code true} (default behavior) to count all items, same result of {@link #getItemCount()}.
+ * - Provide {@code false} to count only main items (headers and footers are EXCLUDED).
+ *
+ * Note: This method cannot be overridden since internal methods rely on it.
+ *
+ * @return the total number of items held by the adapter, with or without headers and footers,
+ * depending by the provided parameter
+ * @see #getItemCount()
+ * @see #getItemCountOfTypes(Integer...)
+ * @since 5.0.0-rc1
+ */
+ public final int getMainItemCount() {
+ return getItemCount() - mScrollableHeaders.size() - mScrollableFooters.size();
}
/**
@@ -551,12 +597,18 @@ public final int getItemCount() {
* @param viewTypes the viewTypes to count
* @return number of the viewTypes counted
* @see #getItemCount()
- * @see #getItemCountOfTypesUntil(int, Integer...)
+ * @see #getMainItemCount()
* @see #isEmpty()
* @since 5.0.0-b1
*/
- public int getItemCountOfTypes(Integer... viewTypes) {
- return getItemCountOfTypesUntil(getItemCount(), viewTypes);
+ public final int getItemCountOfTypes(Integer... viewTypes) {
+ List viewTypeList = Arrays.asList(viewTypes);
+ int count = 0;
+ for (int i = 0; i < getItemCount(); i++) {
+ if (viewTypeList.contains(getItemViewType(i)))
+ count++;
+ }
+ return count;
}
/**
@@ -566,18 +618,18 @@ public int getItemCountOfTypes(Integer... viewTypes) {
* @param position the position limit where to stop counting (included)
* @param viewTypes the viewTypes to count
* @see #getItemCount()
+ * @see #getMainItemCount()
* @see #getItemCountOfTypes(Integer...)
* @see #isEmpty()
* @since 5.0.0-b5
+ * @deprecated Not used anymore.
*/
- //TODO: deprecation?
- public int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... viewTypes) {
+ @Deprecated
+ public final int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... viewTypes) {
List viewTypeList = Arrays.asList(viewTypes);
int count = 0;
for (int i = 0; i < position; i++) {
- //Privilege faster counting if autoMap is active
- if ((autoMap && viewTypeList.contains(mItems.get(i).getLayoutRes())) ||
- viewTypeList.contains(getItemViewType(i)))
+ if (viewTypeList.contains(getItemViewType(i)))
count++;
}
return count;
@@ -597,14 +649,34 @@ public boolean isEmpty() {
}
/**
- * Retrieve the global position of the Item in the Adapter list.
+ * Retrieves the global position of the item in the Adapter list.
+ * If no scrollable Headers are added, the global position coincides with the cardinal position.
+ * This method cannot be overridden since the entire library relies on it.
*
* @param item the item to find
* @return the global position in the Adapter if found, -1 otherwise
* @since 5.0.0-b1
*/
- public int getGlobalPositionOf(@NonNull IFlexible item) {
- return item != null && mItems != null && !mItems.isEmpty() ? mItems.indexOf(item) : -1;
+ public final int getGlobalPositionOf(@NonNull IFlexible item) {
+ return item != null ? mItems.indexOf(item) : -1;
+ }
+
+ /**
+ * Retrieves the position of the Main item in the Adapter list excluding the scrollable Headers.
+ * If no scrollable Headers are added, the cardinal position coincides with the global position.
+ * Note:
+ *
- This method is NOT suitable to call when managing items: ALL insert, remove, move and
+ * swap operations, should done with global position {@link #getGlobalPositionOf(IFlexible)}.
+ *
- This method cannot be overridden.
+ *
+ * @param item the item to find
+ * @return the position in the Adapter excluding the Scrollable Headers, -1 otherwise
+ * @since 5.0.0-rc1
+ */
+ public final int getCardinalPositionOf(@NonNull IFlexible item) {
+ int position = getGlobalPositionOf(item);
+ if (position > mScrollableHeaders.size()) position -= mScrollableHeaders.size();
+ return position;
}
/**
@@ -615,14 +687,14 @@ public int getGlobalPositionOf(@NonNull IFlexible item) {
* @since 2.0.0
*/
public boolean contains(@NonNull T item) {
- return item != null && mItems != null && mItems.contains(item);
+ return item != null && mItems.contains(item);
}
/**
* New method to extract the new position where the item should lay.
- * Note: The Comparator should be customized to support all the types of items
- * this Adapter is displaying or a ClassCastException will be raised.
- * If Comparator is {@code null} the returned position is 0.
+ * Note: The {@code Comparator} object should be customized to support all
+ * types of items this Adapter is managing or a {@code ClassCastException} will be raised.
+ * If the {@code Comparator} is {@code null} the returned position is 0 (first position).
*
* @param item the item to evaluate the insertion
* @param comparator the Comparator object with the logic to sort the list
@@ -630,10 +702,10 @@ public boolean contains(@NonNull T item) {
* @since 5.0.0-b7
*/
public int calculatePositionFor(@NonNull Object item, @Nullable Comparator comparator) {
- //There's nothing to compare
+ // There's nothing to compare
if (comparator == null) return 0;
- //Header is visible
+ // Header is visible
if (item instanceof ISectionable) {
IHeader header = ((ISectionable) item).getHeader();
if (header != null && !header.isHidden()) {
@@ -642,8 +714,8 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa
Collections.sort(sortedList, comparator);
int itemPosition = mItems.indexOf(item);
int headerPosition = getGlobalPositionOf(header);
- //#143 - calculatePositionFor() missing a +1 when addItem (fixed by condition: itemPosition != -1)
- //fix represents the situation when item is before the target position (used in moveItem)
+ // #143 - calculatePositionFor() missing a +1 when addItem (fixed by condition: itemPosition != -1)
+ // fix represents the situation when item is before the target position (used in moveItem)
int fix = itemPosition != -1 && itemPosition < headerPosition ? 0 : 1;
int result = headerPosition + sortedList.indexOf(item) + fix;
if (DEBUG) {
@@ -654,7 +726,7 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa
return result;
}
}
- //All other cases
+ // All other cases
List sortedList = new ArrayList(mItems);
if (!sortedList.contains(item)) sortedList.add(item);
Collections.sort(sortedList, comparator);
@@ -663,6 +735,280 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa
return Math.max(0, sortedList.indexOf(item));
}
+ /*------------------------------------*/
+ /* SCROLLABLE HEADERS/FOOTERS METHODS */
+ /*------------------------------------*/
+
+ /**
+ * @return unmodifiable list of Scrollable Headers currently held by the Adapter
+ * @see #addScrollableHeader(IFlexible)
+ * @see #addScrollableHeaderWithDelay(IFlexible, long, boolean)
+ * @since 5.0.0-rc1
+ */
+ public final List getScrollableHeaders() {
+ return Collections.unmodifiableList(mScrollableHeaders);
+ }
+
+ /**
+ * @return unmodifiable list of Scrollable Footers currently held by the Adapter
+ * @see #addScrollableFooter(IFlexible)
+ * @see #addScrollableFooterWithDelay(IFlexible, long, boolean)
+ * @since 5.0.0-rc1
+ */
+ public final List getScrollableFooters() {
+ return Collections.unmodifiableList(mScrollableFooters);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final boolean isScrollableHeaderOrFooter(int position) {
+ T item = getItem(position);
+ return item != null && mScrollableHeaders.contains(item) || mScrollableFooters.contains(item);
+ }
+
+ /**
+ * Adds a Scrollable Header.
+ * Scrollable Headers have the following characteristic:
+ *
+ * - lay always before any main item.
+ * - cannot be selectable nor draggable.
+ * - cannot be inserted twice, but many can be inserted.
+ * - any new header will be inserted before the existent.
+ * - can be of any type so they can be bound at runtime with any data inside.
+ * - won't be filtered because they won't be part of the main list, but added separately
+ * at the initialization phase
+ * - can be added and removed with certain delay.
+ *
+ *
+ * @param headerItem the header item to be added
+ * @return true if the header has been successfully added, false if the header already exists
+ * @see #getScrollableHeaders()
+ * @see #addScrollableHeaderWithDelay(IFlexible, long, boolean)
+ * @since 5.0.0-rc1
+ */
+ //TODO: Endless Top Scrolling
+ public final boolean addScrollableHeader(@NonNull T headerItem) {
+ if (DEBUG) Log.d(TAG, "Add scrollable header " + getClassName(headerItem));
+ if (!mScrollableHeaders.contains(headerItem)) {
+ headerItem.setSelectable(false);
+ headerItem.setDraggable(false);
+ int progressFix = 0;//(headerItem == mProgressItem) ? mScrollableHeaders.size() : 0;
+ mScrollableHeaders.add(headerItem);
+ setScrollAnimate(true); //Headers will scroll animate
+ performInsert(progressFix, Collections.singletonList(headerItem), true);
+ setScrollAnimate(false);
+ return true;
+ } else {
+ Log.w(TAG, "Scrollable header " + getClassName(headerItem) + " already exists");
+ return false;
+ }
+ }
+
+ /**
+ * Adds a Scrollable Footer.
+ * Scrollable Footers have the following characteristic:
+ *
+ * - lay always after any main item.
+ * - cannot be selectable nor draggable.
+ * - cannot be inserted twice, but many can be inserted.
+ * - cannot scroll animate, when inserted for the first time.
+ * - any new footer will be inserted after the existent.
+ * - can be of any type so they can be bound at runtime with any data inside.
+ * - won't be filtered because they won't be part of the main list, but added separately
+ * at the initialization phase
+ * - can be added and removed with certain delay.
+ * - endless {@code progressItem} is handled as a Scrollable Footer, but it will be always
+ * displayed between the main items and the others footers.
+ *
+ *
+ * @param footerItem the footer item to be added
+ * @return true if the footer has been successfully added, false if the footer already exists
+ * @see #getScrollableFooters()
+ * @see #addScrollableFooterWithDelay(IFlexible, long, boolean)
+ * @since 5.0.0-rc1
+ */
+ public final boolean addScrollableFooter(@NonNull T footerItem) {
+ if (!mScrollableFooters.contains(footerItem)) {
+ if (DEBUG) Log.d(TAG, "Add scrollable footer " + getClassName(footerItem));
+ footerItem.setSelectable(false);
+ footerItem.setDraggable(false);
+ int progressFix = (footerItem == mProgressItem) ? mScrollableFooters.size() : 0;
+ //Prevent wrong position after a possible updateDataSet
+ if (progressFix > 0 && mScrollableFooters.size() > 0) {
+ mScrollableFooters.add(0, footerItem);
+ } else {
+ mScrollableFooters.add(footerItem);
+ }
+ performInsert(getItemCount() - progressFix, Collections.singletonList(footerItem), true);
+ return true;
+ } else {
+ Log.w(TAG, "Scrollable footer " + getClassName(footerItem) + " already exists");
+ return false;
+ }
+ }
+
+ /**
+ * Removes the provided Scrollable Header.
+ *
+ * @param headerItem the header to remove
+ * @see #removeScrollableHeaderWithDelay(IFlexible, long)
+ * @see #removeAllScrollableHeaders()
+ * @since 5.0.0-rc1
+ */
+ public final void removeScrollableHeader(@NonNull T headerItem) {
+ if (mScrollableHeaders.remove(headerItem)) {
+ if (DEBUG) Log.d(TAG, "Remove scrollable header " + getClassName(headerItem));
+ performRemove(headerItem, true);
+ }
+ }
+
+ /**
+ * Removes the provided Scrollable Footer.
+ *
+ * @param footerItem the footer to remove
+ * @see #removeScrollableFooterWithDelay(IFlexible, long)
+ * @see #removeAllScrollableFooters()
+ * @since 5.0.0-rc1
+ */
+ public final void removeScrollableFooter(@NonNull T footerItem) {
+ if (mScrollableFooters.remove(footerItem)) {
+ if (DEBUG) Log.d(TAG, "Remove scrollable footer " + getClassName(footerItem));
+ performRemove(footerItem, true);
+ }
+ }
+
+ /**
+ * Removes all Scrollable Headers at once.
+ *
+ * @see #removeScrollableHeader(IFlexible)
+ * @see #removeScrollableHeaderWithDelay(IFlexible, long)
+ * @since 5.0.0-rc1
+ */
+ public final void removeAllScrollableHeaders() {
+ if (mScrollableHeaders.size() > 0) {
+ if (DEBUG) Log.d(TAG, "Remove all scrollable headers");
+ mItems.removeAll(mScrollableHeaders);
+ notifyItemRangeRemoved(0, mScrollableHeaders.size());
+ mScrollableHeaders.clear();
+ }
+ }
+
+ /**
+ * Removes all Scrollable Footers at once.
+ *
+ * @see #removeScrollableFooter(IFlexible)
+ * @see #removeScrollableFooterWithDelay(IFlexible, long)
+ * @since 5.0.0-rc1
+ */
+ public final void removeAllScrollableFooters() {
+ if (mScrollableFooters.size() > 0) {
+ if (DEBUG) Log.d(TAG, "Remove all scrollable footers");
+ mItems.removeAll(mScrollableFooters);
+ notifyItemRangeRemoved(getItemCount() - 1 - mScrollableHeaders.size(), mScrollableFooters.size());
+ mScrollableFooters.clear();
+ }
+ }
+
+ /**
+ * Same as {@link #addScrollableHeader(IFlexible)} but with a delay and the possibility to
+ * scroll to it.
+ *
+ * @param headerItem the header item to be added
+ * @param delay the delay in ms
+ * @param scrollToPosition true to scroll to the header item position once it has been added
+ * @see #addScrollableHeader(IFlexible)
+ * @since 5.0.0-rc1
+ */
+ public final void addScrollableHeaderWithDelay(@NonNull final T headerItem, @IntRange(from = 0) long delay,
+ final boolean scrollToPosition) {
+ if (DEBUG)
+ Log.d(TAG, "Enqueued adding scrollable header (" + delay + "ms) " + getClassName(headerItem));
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (addScrollableHeader(headerItem) && scrollToPosition)
+ performScroll(getGlobalPositionOf(headerItem));
+ }
+ }, delay);
+ }
+
+ /**
+ * Same as {@link #addScrollableFooter(IFlexible)} but with a delay and the possibility to
+ * scroll to it.
+ *
+ * @param footerItem the footer item to be added
+ * @param delay the delay in ms
+ * @param scrollToPosition true to scroll to the footer item position once it has been added
+ * @see #addScrollableFooter(IFlexible)
+ * @since 5.0.0-rc1
+ */
+ public final void addScrollableFooterWithDelay(@NonNull final T footerItem, @IntRange(from = 0) long delay,
+ final boolean scrollToPosition) {
+ if (DEBUG)
+ Log.d(TAG, "Enqueued adding scrollable footer (" + delay + "ms) " + getClassName(footerItem));
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (addScrollableFooter(footerItem) && scrollToPosition)
+ performScroll(getGlobalPositionOf(footerItem));
+ }
+ }, delay);
+ }
+
+ /**
+ * Same as {@link #removeScrollableHeader(IFlexible)} but with a delay.
+ *
+ * @param headerItem the header item to be removed
+ * @param delay the delay in ms
+ * @see #removeScrollableHeader(IFlexible)
+ * @see #removeAllScrollableHeaders()
+ * @since 5.0.0-rc1
+ */
+ public final void removeScrollableHeaderWithDelay(@NonNull final T headerItem, @IntRange(from = 0) long delay) {
+ if (DEBUG)
+ Log.d(TAG, "Enqueued removing scrollable header (" + delay + "ms) " + getClassName(headerItem));
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ removeScrollableHeader(headerItem);
+ }
+ }, delay);
+ }
+
+ /**
+ * Same as {@link #removeScrollableFooter(IFlexible)} but with a delay.
+ *
+ * @param footerItem the footer item to be removed
+ * @param delay the delay in ms
+ * @see #removeScrollableFooter(IFlexible)
+ * @see #removeAllScrollableFooters()
+ * @since 5.0.0-rc1
+ */
+ public final void removeScrollableFooterWithDelay(@NonNull final T footerItem, @IntRange(from = 0) long delay) {
+ if (DEBUG)
+ Log.d(TAG, "Enqueued removing scrollable footer (" + delay + "ms) " + getClassName(footerItem));
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ removeScrollableFooter(footerItem);
+ }
+ }, delay);
+ }
+
+ /**
+ * Helper method to restore the scrollable headers/footers along with the main items.
+ * After the update and the filter operations.
+ */
+ private void restoreScrollableHeadersAndFooters(List items) {
+ for (T item : mScrollableHeaders)
+ if (items.size() > 0) items.add(0, item);
+ else items.add(item);
+ for (T item : mScrollableFooters)
+ items.add(item);
+ }
+
/*--------------------------*/
/* HEADERS/SECTIONS METHODS */
/*--------------------------*/
@@ -671,8 +1017,10 @@ public int calculatePositionFor(@NonNull Object item, @Nullable Comparator compa
* @return true if orphan headers will be removed when unlinked, false if are kept unlinked
* @see #setRemoveOrphanHeaders(boolean)
* @since 5.0.0-b6
+ * @deprecated Orphan headers methods series: There's no advantage to keep in the adapter such
+ * references, most of the time, user has the control on the items to remove, headers as well.
*/
- //TODO: deprecation?
+ @Deprecated
public boolean isRemoveOrphanHeaders() {
return removeOrphanHeaders;
}
@@ -685,23 +1033,27 @@ public boolean isRemoveOrphanHeaders() {
* @return this Adapter, so the call can be chained
* @see #getOrphanHeaders()
* @since 5.0.0-b6
+ * @deprecated Orphan headers methods series: There's no advantage to keep in the adapter such
+ * references, most of the time, user has the control on the items to remove, headers as well.
*/
- //TODO: deprecation?
+ @Deprecated
public FlexibleAdapter setRemoveOrphanHeaders(boolean removeOrphanHeaders) {
+ if (DEBUG) Log.i(TAG, "Set removeOrphanHeaders=" + removeOrphanHeaders);
this.removeOrphanHeaders = removeOrphanHeaders;
return this;
}
/**
- * Setting to automatically unlink the just deleted header from items having that header linked.
+ * Setting to automatically unlink the deleted header from items having that header linked.
* Default value is {@code false}.
*
- * @param unlinkOnRemoveHeader true to unlink also all items with the just deleted header,
- * false otherwise
+ * @param unlinkOnRemoveHeader true to unlink the deleted header from items having that header
+ * linked, false otherwise
* @return this Adapter, so the call can be chained
* @since 5.0.0-b6
*/
public FlexibleAdapter setUnlinkAllItemsOnRemoveHeaders(boolean unlinkOnRemoveHeader) {
+ if (DEBUG) Log.i(TAG, "Set unlinkOnRemoveHeader=" + unlinkOnRemoveHeader);
this.unlinkOnRemoveHeader = unlinkOnRemoveHeader;
return this;
}
@@ -713,8 +1065,10 @@ public FlexibleAdapter setUnlinkAllItemsOnRemoveHeaders(boolean unlinkOnRemoveHe
* @return the list of the orphan headers collected until this moment
* @see #setRemoveOrphanHeaders(boolean)
* @since 5.0.0-b6
+ * @deprecated Orphan headers methods series: There's no advantage to keep in the adapter such
+ * references, most of the time, user has the control on the items to remove, headers as well.
*/
- //TODO: deprecation?
+ @Deprecated
@NonNull
public List getOrphanHeaders() {
return mOrphanHeaders;
@@ -729,8 +1083,10 @@ public List getOrphanHeaders() {
* @param header the header item
* @return this Adapter, so the call can be chained
* @since 5.0.0-b6
+ * @deprecated We simply can use {@code item.setHeader(header)}. Also, it was not obvious
+ * this method also show header, for that we should use add item or section.
*/
- //TODO: deprecation?
+ @Deprecated
public FlexibleAdapter linkHeaderTo(@NonNull T item, @NonNull IHeader header) {
linkHeaderTo(item, header, Payload.LINK);
if (header.isHidden() && headersShown) {
@@ -745,8 +1101,10 @@ public FlexibleAdapter linkHeaderTo(@NonNull T item, @NonNull IHeader header) {
*
* @param item the item that holds the header
* @since 5.0.0-b6
+ * @deprecated We simply can use {@code item.setHeader(null)}. Also, it was not obvious
+ * this method also hide header, for that we should use remove item or section.
*/
- //TODO: deprecation?
+ @Deprecated
public IHeader unlinkHeaderFrom(@NonNull T item) {
IHeader header = unlinkHeaderFrom(item, Payload.UNLINK);
if (header != null && !header.isHidden()) {
@@ -773,7 +1131,7 @@ public List getHeaderItems() {
/**
* @param item the item to check
- * @return true if the item is an instance of {@link IHeader} interface
+ * @return true if the item is an instance of {@link IHeader} interface, false otherwise
* @since 5.0.0-b6
*/
public boolean isHeader(T item) {
@@ -781,7 +1139,7 @@ public boolean isHeader(T item) {
}
/**
- * Helper for the Adapter to check if an item holds a header
+ * Helper method to check if an item holds a header.
*
* @param item the identified item
* @return true if the item holds a header, false otherwise
@@ -794,7 +1152,7 @@ public boolean hasHeader(@NonNull T item) {
/**
* Checks if the item has a header and that header is the same of the provided one.
*
- * @param item the item supposing having a header
+ * @param item the item supposing having the header
* @param header the header to compare
* @return true if the item has a header and it is the same of the provided one, false otherwise
* @since 5.0.0-b6
@@ -805,7 +1163,7 @@ public boolean hasSameHeader(@NonNull T item, @NonNull IHeader header) {
}
/**
- * Provides the header of the specified Sectionable.
+ * Provides the header of the provided {@code ISectionable} item.
*
* @param item the ISectionable item holding a header
* @return the header of the passed Sectionable, null otherwise
@@ -825,11 +1183,10 @@ public IHeader getHeaderOf(@NonNull T item) {
* @return the IHeader item linked to the specified item position
* @since 5.0.0-b6
*/
- //TODO: rename to getSectionByItemPosition?
public IHeader getSectionHeader(@IntRange(from = 0) int position) {
- //Headers are not visible nor sticky
+ // Headers are not visible nor sticky
if (!headersShown) return null;
- //When headers are visible and sticky, get the previous header
+ // When headers are visible and sticky, get the previous header
for (int i = position; i >= 0; i--) {
T item = getItem(i);
if (isHeader(item)) return (IHeader) item;
@@ -844,8 +1201,10 @@ public IHeader getSectionHeader(@IntRange(from = 0) int position) {
* @param header the header/section item
* @return the index of the specified header/section
* @since 5.0.0-b6
+ * @deprecated The library doesn't use the concept of "Section index": we don't add sections
+ * using "Section index".
*/
- //TODO: deprecation?
+ @Deprecated
public int getSectionIndex(@NonNull IHeader header) {
int position = getGlobalPositionOf(header);
return getSectionIndex(position);
@@ -858,8 +1217,10 @@ public int getSectionIndex(@NonNull IHeader header) {
* @param position any item position
* @return the index of the specified item position
* @since 5.0.0-b6
+ * @deprecated The library doesn't use the concept of "Section index": we don't add sections
+ * using "Section index".
*/
- //TODO: deprecation?
+ @Deprecated
public int getSectionIndex(@IntRange(from = 0) int position) {
int sectionIndex = 0;
for (int i = 0; i <= position; i++) {
@@ -869,10 +1230,10 @@ public int getSectionIndex(@IntRange(from = 0) int position) {
}
/**
- * Provides all the items that belongs to the section represented by the specified header.
+ * Provides all the items that belongs to the section represented by the provided header.
*
- * @param header the header that represents the section
- * @return NonNull list of all items in the specified section.
+ * @param header the {@code IHeader} item that represents the section
+ * @return NonNull list of all items in the provided section
* @since 5.0.0-b6
*/
@NonNull
@@ -888,10 +1249,11 @@ public List getSectionItems(@NonNull IHeader header) {
}
/**
- * Provides all the item positions that belongs to the section represented by the specified header.
+ * Provides all the item positions that belongs to the section represented by the provided
+ * header.
*
- * @param header the header that represents the section
- * @return NonNull list of all item positions in the specified section.
+ * @param header the {@code IHeader} item that represents the section
+ * @return NonNull list of all item positions in the provided section
* @since 5.0.0-b8
*/
@NonNull
@@ -914,29 +1276,30 @@ public boolean areHeadersShown() {
}
/**
- * Returns if Adapter will display sticky headers on the top.
+ * Returns if Adapter can actually display sticky headers on the top.
*
* @return true if headers can be sticky, false if headers are scrolled together with all items
* @since 5.0.0-b6
*/
public boolean areHeadersSticky() {
- return headersSticky;
+ return mStickyHeaderHelper != null;
}
/**
* Enables the sticky header functionality.
- * Headers can be sticky only if they are shown. Command is otherwise ignored!
- * NOTE:
- *
- You must read {@link #getStickySectionHeadersHolder()}.
+ * Headers can be sticky only if they are shown.
+ * Note:
+ *
- You must read {@link #getStickyHeaderContainer()}.
*
- Sticky headers are now clickable as any Views, but cannot be dragged nor swiped.
*
- Content and linkage are automatically updated.
*
* @return this Adapter, so the call can be chained
- * @see #getStickySectionHeadersHolder()
+ * @see #getStickyHeaderContainer()
* @since 5.0.0-b6
+ * @deprecated Use {@link #setStickyHeaders(boolean)} with {@code true} as parameter.
*/
- //TODO: deprecation use setStickyHeaders(true)?
- public FlexibleAdapter enableStickyHeaders() {
+ @Deprecated
+ public FlexibleAdapter enableStickyHeader() {
return setStickyHeaders(true);
}
@@ -944,27 +1307,96 @@ public FlexibleAdapter enableStickyHeaders() {
* Disables the sticky header functionality.
*
* @since 5.0.0-b6
+ * @deprecated Use {@link #setStickyHeaders(boolean)} with {@code false} as parameter.
*/
- //TODO: deprecation use setStickyHeaders(false)?
+ @Deprecated
public void disableStickyHeaders() {
setStickyHeaders(false);
}
- private FlexibleAdapter setStickyHeaders(final boolean sticky) {
+ /**
+ * Enables/Disables the sticky header feature with default sticky layout container.
+ * Note:
+ *
+ * - You should consider to display headers with {@link #setDisplayHeadersAtStartUp(boolean)}:
+ * Feature can enabled/disabled freely, but if headers are hidden nothing will happen.
+ * - Only in case of "Sticky Header" items you must provide {@code true} to the
+ * constructor: {@link FlexibleViewHolder#FlexibleViewHolder(View, FlexibleAdapter, boolean)}.
+ * - Optionally, you can set a custom sticky layout container that must be already
+ * inflated.
+ *
Check {@link #setStickyHeaders(boolean, ViewGroup)}.
+ * - Optionally, you can set a layout elevation: Header item elevation is used first,
+ * if not set, default elevation of {@code 21f} pixel is used.
+ *
- Sticky headers are clickable as any views, but cannot be dragged nor swiped.
+ * - Content and linkage are automatically updated.
+ * - Sticky layout container is fade-in and fade-out animated when feature
+ * is respectively enabled and disabled.
+ * - Sticky container can be elevated only if the header item layout has elevation (Do not
+ * exaggerate with elevation).
+ *
+ *
+ * Important! In order to display the Refresh circle AND the FastScroller on the Top of
+ * the sticky container, the RecyclerView must be wrapped with a {@code FrameLayout} as following:
+ *
+ * <FrameLayout
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent">
+ *
+ * <android.support.v7.widget.RecyclerView
+ * android:id="@+id/recycler_view"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent"/>
+ *
+ * </FrameLayout>
+ *
+ *
+ * @param sticky true to initialize sticky headers with default container, false to disable them
+ * @return this Adapter, so the call can be chained
+ * @throws IllegalStateException if this Adapter was not attached to the RecyclerView
+ * @see #setStickyHeaders(boolean, ViewGroup)
+ * @see #setDisplayHeadersAtStartUp(boolean)
+ * @see #setStickyHeaderElevation(float)
+ * @since 5.0.0-rc1
+ */
+ public FlexibleAdapter setStickyHeaders(boolean sticky) {
+ return setStickyHeaders(sticky, mStickyContainer);
+ }
+
+ /**
+ * Enables/Disables the sticky header feature with a custom sticky layout container.
+ * Important: Read the javaDoc of the overloaded method {@link #setStickyHeaders(boolean)}.
+ *
+ * @param sticky true to initialize sticky headers, false to disable them
+ * @param stickyContainer user defined and already inflated sticky layout that will
+ * hold the sticky header itemViews
+ * @return this Adapter, so the call can be chained
+ * @throws IllegalStateException if this Adapter was not attached to the RecyclerView
+ * @see #setStickyHeaders(boolean)
+ * @see #setDisplayHeadersAtStartUp(boolean)
+ * @see #setStickyHeaderElevation(float)
+ * @since 5.0.0-rc1
+ */
+ public FlexibleAdapter setStickyHeaders(final boolean sticky, @NonNull ViewGroup stickyContainer) {
+ if (DEBUG) Log.i(TAG, "Set stickyHeaders=" + sticky + " (in Post!)" +
+ (stickyContainer != null ? " with user defined Sticky Container" : ""));
+
+ // With user defined container
+ mStickyContainer = stickyContainer;
+
+ // Run in post to be sure about the RecyclerView initialization
mHandler.post(new Runnable() {
@Override
public void run() {
- // Add or Remove the sticky headers
+ // Enable or Disable the sticky headers layout
if (sticky) {
- headersSticky = true;
- if (mStickyHeaderHelper == null)
- mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this, mStickyHeaderChangeListener);
- if (!mStickyHeaderHelper.isAttachedToRecyclerView())
+ if (mStickyHeaderHelper == null) {
+ mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this,
+ mStickyHeaderChangeListener, mStickyContainer);
mStickyHeaderHelper.attachToRecyclerView(mRecyclerView);
- if (DEBUG) Log.i(TAG, "Sticky headers enabled");
+ if (DEBUG) Log.i(TAG, "Sticky headers enabled");
+ }
} else if (mStickyHeaderHelper != null) {
- headersSticky = false;
- mStickyHeaderHelper.detachFromRecyclerView(mRecyclerView);
+ mStickyHeaderHelper.detachFromRecyclerView();
mStickyHeaderHelper = null;
if (DEBUG) Log.i(TAG, "Sticky headers disabled");
}
@@ -973,6 +1405,35 @@ public void run() {
return this;
}
+ /**
+ * Gets the layout elevation for sticky header.
+ * Note: This setting is ignored if the header item has already an elevation. The
+ * header elevation overrides this setting.
+ *
+ * @return the elevation in pixel
+ * @see #setStickyHeaderElevation(float)
+ * @since 5.0.0-rc1
+ */
+ public float getStickyHeaderElevation() {
+ return mStickyElevation;
+ }
+
+ /**
+ * Sets the elevation for the sticky header layout.
+ * Note: This setting is ignored if the header item has already an elevation. The
+ * header elevation overrides this setting.
+ * Default value is 0.
+ *
+ * @param stickyElevation the elevation in pixel
+ * @return this Adapter, so the call can be chained
+ * @see #getStickyHeaderElevation()
+ * @since 5.0.0-rc1
+ */
+ public FlexibleAdapter setStickyHeaderElevation(@FloatRange(from = 0) float stickyElevation) {
+ mStickyElevation = stickyElevation;
+ return this;
+ }
+
/**
* Returns the ViewGroup (FrameLayout) that will hold the headers when sticky.
* INCLUDE the predefined layout after the RecyclerView widget, example:
@@ -987,21 +1448,57 @@ public void run() {
*
* @return ViewGroup layout that will hold the sticky header ItemViews
* @since 5.0.0-b6
+ * @deprecated Unused. Use {@link #setStickyHeaders(boolean, ViewGroup)}.
*/
+ @Deprecated
public ViewGroup getStickySectionHeadersHolder() {
- return (ViewGroup) Utils.scanForActivity(mRecyclerView.getContext()).findViewById(R.id.sticky_header_container);
+ if (mStickyContainer == null) {
+ mStickyContainer = (ViewGroup) Utils
+ .scanForActivity(mRecyclerView.getContext())
+ .findViewById(R.id.sticky_header_container);
+ }
+ return mStickyContainer;
}
/**
- * Sets if all headers should be shown at startup. 2 effects are possible depending by
- * the current flag of scrolling animation:
- *
- if enabled, scrolling animations are performed as configured for the header item;
- *
- if disabled, {@code notifyItemInserted()} is instead performed for each header
- * item.
- * Note:
- *
- {@code showAllHeaders()} is already called by this method.
- *
- You should call this method before enabling sticky headers!
- * Default value is {@code false} (headers are NOT shown at startup).
+ * Returns the ViewGroup (FrameLayout) that will hold the headers when sticky.
+ * Set a custom container with {@link #setStickyHeaderContainer(ViewGroup)} before enabling
+ * sticky headers.
+ *
+ * @return ViewGroup layout that will hold the sticky header itemViews
+ * @since 5.0.0-rc1
+ * @deprecated Unused, not needed anymore.
+ */
+ @Deprecated
+ public ViewGroup getStickyHeaderContainer() {
+ return mStickyContainer;
+ }
+
+ /**
+ * Sets a custom {@link ViewGroup} container for Sticky Headers when the default can't be used.
+ *
+ * @param stickyContainer custom container for Sticky Headers
+ * @since 5.0.0-rc1
+ * @deprecated Use {@link #setStickyHeaders(boolean, ViewGroup)}.
+ */
+ @Deprecated
+ public FlexibleAdapter setStickyHeaderContainer(@NonNull ViewGroup stickyContainer) {
+ if (mStickyHeaderHelper != null) {
+ Log.w(TAG, "StickyHeader has been already initialized! Call this method before enabling StickyHeaders");
+ }
+ if (DEBUG && stickyContainer != null)
+ Log.i(TAG, "Set stickyHeaderContainer=" + getClassName(stickyContainer));
+ this.mStickyContainer = stickyContainer;
+ return this;
+ }
+
+ /**
+ * Sets if all headers should be shown at startup.
+ * If called, this method won't trigger {@code notifyItemInserted()} and scrolling
+ * animations are instead performed if the header item was configured with animation:
+ * Headers will be loaded/bound along with others items.
+ * Note: Headers can only be shown or hidden all together.
+ * Default value is {@code false} (headers are not shown at startup).
*
* @param displayHeaders true to display headers, false to keep them hidden
* @return this Adapter, so the call can be chained
@@ -1009,51 +1506,54 @@ public ViewGroup getStickySectionHeadersHolder() {
* @see #setAnimationOnScrolling(boolean)
* @since 5.0.0-b6
*/
- //TODO: deprecation, rename to displayHeadersAtStartUp() with no parameters?
public FlexibleAdapter setDisplayHeadersAtStartUp(boolean displayHeaders) {
if (!headersShown && displayHeaders) {
- showAllHeaders(isAnimationOnScrollingEnabled());
+ showAllHeaders(true);
}
return this;
}
/**
- * Shows all headers in the RecyclerView at their linked position. Not intended to be called at
- * startup.
To display headers at startup please use {@code setDisplayHeadersAtStartUp()}
- * instead.
- * Note: Headers can only be shown or hidden all together.
+ * Shows all headers in the RecyclerView at their linked position.
+ * If called at startup, this method will trigger {@code notifyItemInserted} for a
+ * different loading effect: Headers will be inserted after the items!
+ * Note: Headers can only be shown or hidden all together.
*
+ * @return this Adapter, so the call can be chained
* @see #hideAllHeaders()
* @see #setDisplayHeadersAtStartUp(boolean)
* @since 5.0.0-b1
*/
- public void showAllHeaders() {
+ public FlexibleAdapter showAllHeaders() {
showAllHeaders(false);
+ return this;
}
/**
- * @param init true to skip {@code notifyItemInserted}, false to make the call in Post and
- * notify single insertion
+ * @param init true to skip {@code notifyItemInserted}, false to make the call in Post
+ * and notify single insertion
*/
private void showAllHeaders(boolean init) {
if (init) {
- //No notifyItemInserted!
+ if (DEBUG) Log.i(TAG, "showAllHeaders at startup");
+ // No notifyItemInserted!
showAllHeadersWithReset(true);
} else {
- //In post, let's notifyItemInserted!
+ if (DEBUG) Log.i(TAG, "showAllHeaders with insert notification (in Post!)");
+ // In post, let's notifyItemInserted!
mHandler.post(new Runnable() {
@Override
public void run() {
- //#144 - Check if headers are already shown, discard the call to not duplicate headers
+ // #144 - Check if headers are already shown, discard the call to not duplicate headers
if (headersShown) {
- Log.w(TAG, "Headers already shown OR the method setDisplayHeadersAtStartUp() was already called!");
+ Log.w(TAG, "Double call detected! Headers already shown OR the method showAllHeaders() was already called!");
return;
}
showAllHeadersWithReset(false);
- //#142 - At startup, when scrolling animation is disabled, insert notifications
- // are performed to show headers for the first time. Header item is not visible
- // at position 0: it has to be displayed by scrolling to it. This resolves the
- // first item below sticky header when enabled as well.
+ // #142 - At startup, when insert notifications are performed to show headers
+ // for the first time. Header item is not visible at position 0: it has to be
+ // displayed by scrolling to it. This resolves the first item below sticky
+ // header when enabled as well.
if (mRecyclerView != null) {
int firstVisibleItem = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
if (firstVisibleItem == 0 && isHeader(getItem(0)) && !isHeader(getItem(1))) {
@@ -1069,16 +1569,21 @@ public void run() {
* @param init true to skip the call to notifyItemInserted, false otherwise
*/
private void showAllHeadersWithReset(boolean init) {
- multiRange = true;
int position = 0;
- resetHiddenStatus();//Necessary after the filter and the update
- while (position < mItems.size()) {
- if (showHeaderOf(position, mItems.get(position), init))
- position++;//It's the same element, skip it
+ IHeader sameHeader = null;
+ while (position < getItemCount() - mScrollableFooters.size()) {
+ T item = mItems.get(position);
+ // Reset hidden status! Necessary after the filter and the update
+ IHeader header = getHeaderOf(item);
+ if (header != sameHeader && header != null && !isExpandable((T) header)) {
+ sameHeader = header;
+ header.setHidden(true);
+ }
+ if (showHeaderOf(position, item, init))
+ position++; //It's the same element, skip it
position++;
}
headersShown = true;
- multiRange = false;
}
/**
@@ -1086,27 +1591,20 @@ private void showAllHeadersWithReset(boolean init) {
*
* @param position the position where the header will be displayed
* @param item the item that holds the header
- * @param init for silent initialization
+ * @param init for silent initialization: skip notifyItemInserted
* @since 5.0.0-b1
*/
private boolean showHeaderOf(int position, @NonNull T item, boolean init) {
- //Take the header
+ // Take the header
IHeader header = getHeaderOf(item);
- //Check header existence
+ // Check header existence
if (header == null || getPendingRemovedItem(item) != null) return false;
if (header.isHidden()) {
if (DEBUG) Log.v(TAG, "Showing header at position " + position + " header=" + header);
header.setHidden(false);
- if (init) {//Skip notifyItemInserted!
- if (position < mItems.size()) {
- mItems.add(position, (T) header);
- } else {
- mItems.add((T) header);
- }
- return true;
- } else {
- return addItem(position, (T) header);
- }
+ // Insert header, but skip notifyItemInserted when init=true!
+ performInsert(position, Collections.singletonList((T) header), !init);
+ return true;
}
return false;
}
@@ -1116,6 +1614,7 @@ private boolean showHeaderOf(int position, @NonNull T item, boolean init) {
* Headers can be shown or hidden all together.
*
* @see #showAllHeaders()
+ * @see #setDisplayHeadersAtStartUp(boolean)
* @since 5.0.0-b1
*/
public void hideAllHeaders() {
@@ -1123,20 +1622,20 @@ public void hideAllHeaders() {
@Override
public void run() {
multiRange = true;
- //Hide orphan headers first
- for (IHeader header : getOrphanHeaders()) {
- hideHeader(getGlobalPositionOf(header), header);
- }
- //Hide linked headers
- int position = mItems.size() - 1;
- while (position >= 0) {
+ // Hide linked headers between Scrollable Headers and Footers
+ int position = getItemCount() - mScrollableFooters.size() - 1;
+ while (position >= Math.max(0, mScrollableHeaders.size() - 1)) {
T item = mItems.get(position);
if (isHeader(item))
hideHeader(position, (IHeader) item);
position--;
}
headersShown = false;
- setStickyHeaders(false);
+ // Clear the header currently sticky
+ if (areHeadersSticky()) {
+ mStickyHeaderHelper.clearHeaderWithAnimation();
+ }
+ // setStickyHeaders(false);
multiRange = false;
}
});
@@ -1149,9 +1648,9 @@ public void run() {
* @since 5.0.0-b1
*/
private boolean hideHeaderOf(@NonNull T item) {
- //Take the header
+ // Take the header
IHeader header = getHeaderOf(item);
- //Check header existence
+ // Check header existence
return header != null && !header.isHidden() && hideHeader(getGlobalPositionOf(header), header);
}
@@ -1159,7 +1658,7 @@ private boolean hideHeader(int position, IHeader header) {
if (position >= 0) {
if (DEBUG) Log.v(TAG, "Hiding header at position " + position + " header=" + header);
header.setHidden(true);
- //Remove and notify removals
+ // Remove and notify removals
mItems.remove(position);
notifyItemRemoved(position);
return true;
@@ -1167,23 +1666,6 @@ private boolean hideHeader(int position, IHeader header) {
return false;
}
- /**
- * Helper method to ensure that all current headers are hidden before they are shown again.
- * This method is already called inside {@link #showAllHeadersWithReset(boolean)}.
- * This is necessary when {@link #setDisplayHeadersAtStartUp(boolean)} is set true and also
- * if an Activity/Fragment has been closed and then reopened. We need to reset hidden status,
- * the process is very fast.
- *
- * @since 5.0.0-b6
- */
- private void resetHiddenStatus() {
- for (T item : mItems) {
- IHeader header = getHeaderOf(item);
- if (header != null && !isExpandable((T) header))
- header.setHidden(true);
- }
- }
-
/**
* Internal method to link the header to the new item.
* Used by the Adapter during the Remove/Restore/Move operations.
@@ -1201,16 +1683,17 @@ private boolean linkHeaderTo(@NonNull T item, @NonNull IHeader header, @Nullable
boolean linked = false;
if (item != null && item instanceof ISectionable) {
ISectionable sectionable = (ISectionable) item;
- //Unlink header only if different
+ // Unlink header only if different
if (sectionable.getHeader() != null && !sectionable.getHeader().equals(header)) {
unlinkHeaderFrom((T) sectionable, Payload.UNLINK);
}
if (sectionable.getHeader() == null && header != null) {
if (DEBUG) Log.v(TAG, "Link header " + header + " to " + sectionable);
+ //TODO: try-catch for when sectionable item has a different header class signature, if so, they just can't accept that header!
sectionable.setHeader(header);
linked = true;
removeFromOrphanList(header);
- //Notify items
+ // Notify items
if (payload != null) {
if (!header.isHidden()) notifyItemChanged(getGlobalPositionOf(header), payload);
if (!item.isHidden()) notifyItemChanged(getGlobalPositionOf(item), payload);
@@ -1240,7 +1723,7 @@ private IHeader unlinkHeaderFrom(@NonNull T item, @Nullable Object payload) {
if (DEBUG) Log.v(TAG, "Unlink header " + header + " from " + sectionable);
sectionable.setHeader(null);
addToOrphanListIfNeeded(header, getGlobalPositionOf(item), 1);
- //Notify items
+ // Notify items
if (payload != null) {
if (!header.isHidden()) notifyItemChanged(getGlobalPositionOf(header), payload);
if (!item.isHidden()) notifyItemChanged(getGlobalPositionOf(item), payload);
@@ -1250,9 +1733,9 @@ private IHeader unlinkHeaderFrom(@NonNull T item, @Nullable Object payload) {
return null;
}
- //TODO: deprecation?
+ @Deprecated
private void addToOrphanListIfNeeded(IHeader header, int positionStart, int itemCount) {
- //Check if the header is not already added (happens after un-linkage with un-success linkage)
+ // Check if the header is not already added (happens after un-linkage with un-success linkage)
if (!mOrphanHeaders.contains(header) && !isHeaderShared(header, positionStart, itemCount)) {
mOrphanHeaders.add(header);
if (DEBUG)
@@ -1260,22 +1743,22 @@ private void addToOrphanListIfNeeded(IHeader header, int positionStart, int item
}
}
- //TODO: deprecation?
+ @Deprecated
private void removeFromOrphanList(IHeader header) {
if (mOrphanHeaders.remove(header) && DEBUG)
Log.v(TAG, "Removed from orphan list [" + mOrphanHeaders.size() + "] Header " + header);
}
- //TODO: deprecation?
+ @Deprecated
private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) {
int firstElementWithHeader = getGlobalPositionOf(header) + 1;
- for (int i = firstElementWithHeader; i < mItems.size(); i++) {
+ for (int i = firstElementWithHeader; i < getItemCount() - mScrollableFooters.size(); i++) {
T item = getItem(i);
- //Another header is met, we can stop here
+ // Another header is met, we can stop here
if (item instanceof IHeader) break;
- //Skip the items under modification
+ // Skip the items under modification
if (i >= positionStart && i < positionStart + itemCount) continue;
- //An element with same header is met
+ // An element with same header is met
if (hasSameHeader(item, header))
return true;
}
@@ -1287,45 +1770,47 @@ private boolean isHeaderShared(IHeader header, int positionStart, int itemCount)
/*---------------------*/
/**
- * Returns the ViewType for all Items depends by the current position.
- * You can override this method to return specific values (don't call super) or you can
+ * Returns the ViewType for all Items depends by the provided position.
+ *
You can override this method to return specific values (don't call super) OR you can
* let this method to call the implementation of {@code IFlexible#getLayoutRes()} so ViewTypes
* are automatically mapped (AutoMap).
*
* @param position position for which ViewType is requested
- * @return if Item is found, any integer value from user layout resource if defined in
+ * @return if item is found, any integer value from user layout resource if defined in
* {@code IFlexible#getLayoutRes()}
* @since 5.0.0-b1
*/
@Override
public int getItemViewType(int position) {
T item = getItem(position);
- //Map the view type if not done yet
+ // Map the view type if not done yet
mapViewTypeFrom(item);
autoMap = true;
return item.getLayoutRes();
}
/**
- * You can override this method to create ViewHolder from inside the Adapter or you can let
+ * You can override this method to create ViewHolder from inside the Adapter OR you can let
* this method to call the implementation of {@code IFlexible#createViewHolder()} to create
- * ViewHolder from inside the Item (AutoMap).
+ * ViewHolder from inside the item (AutoMap).
+ * HELP: To know how to implement AutoMap for ViewTypes please refer to the
+ * FlexibleAdapter Wiki Page
+ * on GitHub.
*
{@inheritDoc}
*
* @return a new ViewHolder that holds a View of the given view type
- * @throws IllegalStateException if {@code IFlexible#createViewHolder()} is not implemented and
- * if this method is not overridden OR if ViewType instance has
- * not been correctly mapped.
+ * @throws IllegalStateException if this method is not overridden OR AutoMap is not active
+ * OR if ViewType instance has not been properly mapped.
* @see IFlexible#createViewHolder(FlexibleAdapter, LayoutInflater, ViewGroup)
* @since 5.0.0-b1
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
T item = getViewTypeInstance(viewType);
- if (item == null) {
- //If everything has been set properly, this should never happen ;-)
- throw new IllegalStateException("ViewType instance has not been correctly mapped for viewType "
- + viewType + " or AutoMap is not active: super() cannot be called.");
+ if (item == null || !autoMap) {
+ // If everything has been set properly, this should never happen ;-)
+ throw new IllegalStateException("ViewType instance not found for viewType " + viewType +
+ ". Override this method or implement the AutoMap properly.");
}
if (mInflater == null) {
mInflater = LayoutInflater.from(parent.getContext());
@@ -1335,12 +1820,15 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType
/**
* You can override this method to bind the items into the corresponding ViewHolder from
- * inside the Adapter or you can let this method to call the implementation of
+ * inside the Adapter OR you can let this method to call the implementation of
* {@code IFlexible#bindViewHolder()} to bind the item inside itself (AutoMap).
+ * HELP: To know how to implement AutoMap for ViewTypes please refer to the
+ * FlexibleAdapter Wiki Page
+ * on GitHub.
*
{@inheritDoc}
*
- * @throws IllegalStateException if {@code IFlexible#bindViewHolder()} is not implemented OR
- * if {@code super()} is called when AutoMap is not active.
+ * @throws IllegalStateException if this method is not overridden OR AutoMap has not been
+ * properly implemented.
* @see IFlexible#bindViewHolder(FlexibleAdapter, RecyclerView.ViewHolder, int, List)
* @since 5.0.0-b1
*/
@@ -1353,50 +1841,42 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
* Same concept of {@code #onBindViewHolder()} but with Payload.
* {@inheritDoc}
*
- * @throws IllegalStateException if {@code IFlexible#bindViewHolder()} is not implemented OR
- * if {@code super()} is called when AutoMap is not active.
+ * @throws IllegalStateException if this method is not overridden OR AutoMap has not been
+ * properly implemented.
* @see IFlexible#bindViewHolder(FlexibleAdapter, RecyclerView.ViewHolder, int, List)
* @see #onBindViewHolder(RecyclerView.ViewHolder, int)
* @since 5.0.0-b1
*/
@Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
+ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position, List payloads) {
if (DEBUG) {
- Log.v(TAG, "onViewBound Holder=" + holder.getClass().getSimpleName() +
- " position=" + position +
- " itemId=" + holder.getItemId() +
- " layoutPosition=" + holder.getLayoutPosition());
- }
- //This check is necessary when using Expandable items, it helps to optimize binding.
- // Expandable items can lay out of the screen during the initialization/refresh
- // as soon as they are expanded one by one.
-// if (holder.getLayoutPosition() > mRecyclerView.getChildCount()) {
-// Log.w(TAG, "onViewBound Skip binding for view out of screen " +
-// holder.getLayoutPosition() + "/" + mRecyclerView.getChildCount());
-// return;
-// }
+ Log.v(TAG, "onViewBound Holder=" + getClassName(holder) + " position=" + position +
+ " itemId=" + holder.getItemId() + " layoutPosition=" + holder.getLayoutPosition());
+ }
if (!autoMap) {
- throw new IllegalStateException("AutoMap is not active: super() cannot be called.");
- }
- //When user scrolls, this line binds the correct selection status
- holder.itemView.setActivated(isSelected(position));
- //Bind the correct view elevation
- if (holder instanceof FlexibleViewHolder) {
- FlexibleViewHolder flexHolder = (FlexibleViewHolder) holder;
- float elevation = flexHolder.getActivationElevation();
- if (holder.itemView.isActivated() && elevation > 0)
- ViewCompat.setElevation(holder.itemView, elevation);
- else if (elevation > 0)//Leave unaltered the default elevation
- ViewCompat.setElevation(holder.itemView, 0);
- }
- //Bind the item
+ // If everything has been set properly, this should never happen ;-)
+ throw new IllegalStateException("AutoMap is not active, this method cannot be called." +
+ " Override this method or implement the AutoMap properly.");
+ }
+ // Bind view activation with current selection
+ super.onBindViewHolder(holder, position, payloads);
+ // Bind the item
T item = getItem(position);
if (item != null) {
holder.itemView.setEnabled(item.isEnabled());
item.bindViewHolder(this, holder, position, payloads);
+ // Avoid to show the double background in case header has transparency
+ // The visibility will be restored when header is reset in StickyHeaderHelper
+ if (areHeadersSticky() && !isFastScroll && mStickyHeaderHelper.getStickyPosition() >= 0 && payloads.isEmpty()) {
+ int headerPos = Utils.findFirstVisibleItemPosition(mRecyclerView.getLayoutManager()) - 1;
+ if (headerPos == position && isHeader(item))
+ holder.itemView.setVisibility(View.INVISIBLE);
+ }
}
- //Endless Scroll
+ // Endless Scroll
onLoadMore(position);
+ // Scroll Animation
+ animateView(holder, position);
}
/*------------------------*/
@@ -1404,39 +1884,133 @@ else if (elevation > 0)//Leave unaltered the default elevation
/*------------------------*/
/**
- * Sets the ProgressItem to be displayed at the end of the list and activate the Loading More
- * functionality.
+ * Evaluates if the Adapter is in Endless Scroll mode. When no more load, this method will
+ * return {@code false}. To enable again the progress item you MUST be set again.
+ *
+ * @return true if the progress item is set, false otherwise
+ * @see #setEndlessProgressItem(IFlexible)
+ * @since 5.0.0-rc1
+ */
+ public boolean isEndlessScrollEnabled() {
+ return endlessScrollEnabled;
+ }
+
+ /**
+ * Provides the current endless page if the page size limit is set, if not set the returned
+ * value is always 1.
+ *
+ * @return the current endless page
+ * @see #getEndlessPageSize()
+ * @see #setEndlessPageSize(int)
+ * @since 5.0.0-rc1
+ */
+ public int getEndlessCurrentPage() {
+ return Math.max(1, mEndlessPageSize > 0 ? getMainItemCount() / mEndlessPageSize : 0);
+ }
+
+ /**
+ * The current setting for the endless page size limit.
+ * Note: This limit is ignored if value is 0.
+ *
+ * @return the page size limit, if the limit is not set, 0 is returned.
+ * @see #getEndlessCurrentPage()
+ * @see #setEndlessPageSize(int)
+ * @since 5.0.0-rc1
+ */
+ public int getEndlessPageSize() {
+ return mEndlessPageSize;
+ }
+
+ /**
+ * Sets the limit to automatically disable the endless feature when coming items size is less
+ * than the page size.
+ * When endless feature is disabled a {@link #notifyItemChanged(int, Object)} with payload
+ * {@link Payload#NO_MORE_LOAD} will be triggered on the progressItem, so you can display a
+ * message or change the views in this item.
+ * Default value is 0 (limit is ignored).
+ *
+ * @param endlessPageSize the size limit for each page
+ * @return this Adapter, so the call can be chained
+ * @since 5.0.0-rc1
+ */
+ public FlexibleAdapter setEndlessPageSize(@IntRange(from = 0) int endlessPageSize) {
+ mEndlessPageSize = endlessPageSize;
+ return this;
+ }
+
+ /**
+ * The current setting for the endless target item count limit.
+ * Note: This limit is ignored if value is 0.
+ *
+ * @return the target items count limit, if the limit is not set, 0 is returned.
+ * @see #setEndlessTargetCount(int)
+ * @since 5.0.0-rc1
+ */
+ public int getEndlessTargetCount() {
+ return mEndlessTargetCount;
+ }
+
+ /**
+ * Sets the limit to automatically disable the endless feature when the total items count in
+ * the Adapter is equals or bigger than the target count.
+ * When endless feature is disabled a {@link #notifyItemChanged(int, Object)} with payload
+ * {@link Payload#NO_MORE_LOAD} will be triggered on the progressItem, so you can display a
+ * message or change the views in this item.
+ * Default value is 0 (limit is ignored).
+ *
+ * @param endlessTargetCount the total items count limit
+ * @return this Adapter, so the call can be chained
+ * @see #getEndlessTargetCount()
+ * @since 5.0.0-rc1
+ */
+ public FlexibleAdapter setEndlessTargetCount(@IntRange(from = 0) int endlessTargetCount) {
+ mEndlessTargetCount = endlessTargetCount;
+ return this;
+ }
+
+ /**
+ * Sets the progressItem to be displayed at the end of the list and activate the Loading More
+ * feature.
* Using this method, the {@link EndlessScrollListener} won't be called so that you can
* handle a click event to load more items upon a user request.
* To correctly implement "Load more upon a user request" check the Wiki page of this library.
*
* @param progressItem the item representing the progress bar
* @return this Adapter, so the call can be chained
+ * @see #isEndlessScrollEnabled()
* @see #setEndlessScrollListener(EndlessScrollListener, IFlexible)
* @since 5.0.0-b8
*/
- public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) {
+ public FlexibleAdapter setEndlessProgressItem(@Nullable T progressItem) {
+ endlessScrollEnabled = progressItem != null;
if (progressItem != null) {
setEndlessScrollThreshold(mEndlessScrollThreshold);
- progressItem.setEnabled(false);
mProgressItem = progressItem;
+ if (DEBUG) {
+ Log.i(TAG, "Set progressItem=" + getClassName(progressItem));
+ Log.i(TAG, "Enabled EndlessScrolling");
+ }
+ } else if (DEBUG) {
+ Log.i(TAG, "Disabled EndlessScrolling");
}
return this;
}
/**
- * Sets the ProgressItem to be displayed at the end of the list and Sets the callback to
- * automatically load more items asynchronously (no further user action is needed but the
- * scroll).
+ * Sets the progressItem to be displayed at the end of the list and Sets the callback to
+ * automatically load more items asynchronously(your duty) (no further user action is needed
+ * but the scroll).
*
* @param endlessScrollListener the callback to invoke the asynchronous loading
* @param progressItem the item representing the progress bar
* @return this Adapter, so the call can be chained
+ * @see #isEndlessScrollEnabled()
* @see #setEndlessProgressItem(IFlexible)
* @since 5.0.0-b6
*/
public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener,
@NonNull T progressItem) {
+ if (DEBUG) Log.i(TAG, "Set endlessScrollListener=" + getClassName(endlessScrollListener));
mEndlessScrollListener = endlessScrollListener;
return setEndlessProgressItem(progressItem);
}
@@ -1450,37 +2024,57 @@ public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener
* @since 5.0.0-b6
*/
public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresholdItems) {
- //Increase visible threshold based on number of columns
+ // Increase visible threshold based on number of columns
if (mRecyclerView != null) {
- int spanCount = getSpanCount(mRecyclerView.getLayoutManager());
+ int spanCount = Utils.getSpanCount(mRecyclerView.getLayoutManager());
thresholdItems = thresholdItems * spanCount;
}
mEndlessScrollThreshold = thresholdItems;
+ if (DEBUG) Log.i(TAG, "Set endlessScrollThreshold=" + mEndlessScrollThreshold);
return this;
}
/**
* This method is called automatically if METHOD A is implemented. If instead you chose the
- * classic way (METHOD B) to bind the items, you have to manually call this method at the end
- * of {@code onBindViewHolder()}.
+ * classic way (METHOD B) to bind the items, you have to manually call this method
+ * at the end of {@code onBindViewHolder()}.
*
* @param position the current binding position
+ * @since 5.0.0-b6
+ *
5.0.0-rc1 Added limits check and progressItem with Scrollable Footers
*/
protected void onLoadMore(int position) {
- if (mProgressItem != null && !mLoading
- && position >= getItemCount() - mEndlessScrollThreshold
- && getGlobalPositionOf(mProgressItem) < 0) {
- mLoading = true;
- mRecyclerView.post(new Runnable() {
- @Override
- public void run() {
- mItems.add(mProgressItem);
- notifyItemInserted(getItemCount());
- if (mEndlessScrollListener != null)
- mEndlessScrollListener.onLoadMore();
+ // Skip everything when loading more is unused OR currently loading
+ if (!isEndlessScrollEnabled() || endlessLoading)
+ return;
+
+ // Check next loading threshold
+ int threshold = getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : mScrollableFooters.size());
+ if (position == getGlobalPositionOf(mProgressItem) || position < threshold) {
+ return;
+ } else if (DEBUG) {
+ Log.v(TAG, "onLoadMore loading=" + endlessLoading + ", position=" + position
+ + ", itemCount=" + getItemCount() + ", threshold=" + mEndlessScrollThreshold
+ + ", inside the threshold? " + (position >= getItemCount() - mEndlessScrollThreshold - (hasSearchText() ? 0 : mScrollableFooters.size())));
+ }
+ // Load more if not loading and inside the threshold
+ endlessLoading = true;
+ // Insertion is in post, as suggested by Android because: java.lang.IllegalStateException:
+ // Cannot call notifyItemInserted while RecyclerView is computing a layout or scrolling
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Clear previous delayed message
+ mHandler.removeMessages(LOAD_MORE_COMPLETE);
+ // Add progressItem if not already shown
+ addScrollableFooter(mProgressItem);
+ // When the listener is not set, loading more is called upon a user request
+ if (mEndlessScrollListener != null) {
+ if (DEBUG) Log.d(TAG, "onLoadMore invoked!");
+ mEndlessScrollListener.onLoadMore(getMainItemCount(), getEndlessCurrentPage());
}
- });
- }
+ }
+ });
}
/**
@@ -1497,61 +2091,78 @@ public void onLoadMoreComplete(@Nullable List newItems) {
}
/**
- * To call to complete the action of the Loading more items.
+ * Call this method to complete the action of the Loading more items.
* When noMoreLoad OR onError OR onCancel, pass empty list or null to hide the
- * progressItem.
+ * progressItem. When limits are set, endless feature will be disabled. To enable
+ * again call {@link #setEndlessProgressItem(IFlexible)}.
* Optionally you can pass a delay time to still display the item with the latest information
- * inside. The message has to be handled inside the bindViewHolder of the item.
+ * inside. The message has to be handled inside the {@code bindViewHolder} of the item.
+ * A {@link #notifyItemChanged(int, Object)} with payload {@link Payload#NO_MORE_LOAD}
+ * will be triggered on the progressItem, so you can display a message or change the views in
+ * this item.
*
* @param newItems the list of the new items, can be empty or null
* @param delay the delay used to remove the progress item or -1 to disable the
- * loading forever and to keep the progress item.
+ * loading forever and to keep the progress item visible.
* @since 5.0.0-b8
+ *
5.0.0-rc1 Added limits check and changed progressItem to Scrollable Footer
*/
+ //TODO: Endless Top Scrolling
public void onLoadMoreComplete(@Nullable List newItems, @IntRange(from = -1) long delay) {
- //Handling the delay
- if (delay < 0) {
- //Disable the Endless functionality and keep the item
- mProgressItem = null;
- } else {
- //Delete the progress item with delay
+ // 1. Calculate new items count
+ int newItemsSize = newItems == null ? 0 : newItems.size();
+ int totalItemCount = newItemsSize + getMainItemCount();
+ // 2. Add any new items
+ if (newItemsSize > 0) {
+ if (DEBUG)
+ Log.v(TAG, "onLoadMore performing adding " + newItemsSize + " new items on Page=" + getEndlessCurrentPage());
+ //TODO: 0 + headers for Endless Top Scrolling
+ addItems(getGlobalPositionOf(mProgressItem), newItems);
+ }
+ // 3. Check if features are enabled and the limits have been reached
+ if (mEndlessPageSize > 0 && newItemsSize < mEndlessPageSize || // Is feature enabled and Not enough items?
+ mEndlessTargetCount > 0 && totalItemCount >= mEndlessTargetCount) { // Is feature enabled and Max limit has been reached?
+ // Disable the EndlessScroll feature
+ setEndlessProgressItem(null);
+ }
+ // 4. Remove the progressItem if needed
+ if (delay > 0 && (newItemsSize == 0 || !isEndlessScrollEnabled())) {
+ if (DEBUG)
+ Log.v(TAG, "onLoadMore enqueued removing progressItem (" + delay + "ms)");
mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay);
+ } else if (isEndlessScrollEnabled()) {
+ hideProgressItem();
}
- //Add the new items or reset the loading status
- if (newItems != null && newItems.size() > 0) {
- if (DEBUG)
- Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!");
- addItems(getItemCount(), newItems);
- //Reset OnLoadMore delayed
- mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L);
- } else {
- noMoreLoad();
+ // 5. Reset the loading status
+ endlessLoading = false;
+ // 6. Eventually notify noMoreLoad
+ if (newItemsSize == 0 || !isEndlessScrollEnabled()) {
+ noMoreLoad(newItemsSize);
}
}
/**
* Called when loading more should continue.
*/
- private void deleteProgressItem() {
- int progressPosition = getGlobalPositionOf(mProgressItem);
- if (progressPosition >= 0) {
- mItems.remove(mProgressItem);
- notifyItemRemoved(progressPosition);
+ private void hideProgressItem() {
+ int positionToNotify = getGlobalPositionOf(mProgressItem);
+ if (positionToNotify >= 0) {
+ if (DEBUG) Log.v(TAG, "onLoadMore remove progressItem");
+ removeScrollableFooter(mProgressItem);
}
}
/**
* Called when no more items are loaded.
*/
- private void noMoreLoad() {
- if (DEBUG) Log.v(TAG, "onLoadMore noMoreLoad!");
- notifyItemChanged(getItemCount() - 1, Payload.NO_MORE_LOAD);
- //Reset OnLoadMore delayed
- mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L);
- }
-
- private void resetOnLoadMore() {
- mLoading = false;
+ private void noMoreLoad(int newItemsSize) {
+ if (DEBUG) Log.i(TAG, "noMoreLoad!");
+ int positionToNotify = getGlobalPositionOf(mProgressItem);
+ if (positionToNotify >= 0)
+ notifyItemChanged(positionToNotify, Payload.NO_MORE_LOAD);
+ if (mEndlessScrollListener != null) {
+ mEndlessScrollListener.noMoreLoad(newItemsSize);
+ }
}
/*--------------------*/
@@ -1559,7 +2170,7 @@ private void resetOnLoadMore() {
/*--------------------*/
/**
- * @return true if autoCollapseOnExpand is enabled, false otherwise
+ * @return true if {@code collapseOnExpand} is enabled, false otherwise
* @since 5.0.0-b8
*/
public boolean isAutoCollapseOnExpand() {
@@ -1567,20 +2178,21 @@ public boolean isAutoCollapseOnExpand() {
}
/**
- * Automatically collapse all previous expanded parents before expand the clicked parent.
- * Default value is disabled.
+ * Automatically collapse all previous expanded parents before expand the new clicked parent.
+ * Default value is {@code false} (disabled).
*
* @param collapseOnExpand true to collapse others items, false to just expand the current
* @return this Adapter, so the call can be chained
* @since 5.0.0-b1
*/
public FlexibleAdapter setAutoCollapseOnExpand(boolean collapseOnExpand) {
+ if (DEBUG) Log.i(TAG, "Set autoCollapseOnExpand=" + collapseOnExpand);
this.collapseOnExpand = collapseOnExpand;
return this;
}
/**
- * @return true if autoScrollOnExpand is enabled, false otherwise
+ * @return true if {@code scrollOnExpand} is enabled, false otherwise
* @since 5.0.0-b8
*/
public boolean isAutoScrollOnExpand() {
@@ -1589,15 +2201,16 @@ public boolean isAutoScrollOnExpand() {
/**
* Automatically scroll the clicked expandable item to the first visible position.
- * Default value is disabled.
- * This works ONLY in combination with {@link SmoothScrollLinearLayoutManager} or with
- * {@link SmoothScrollGridLayoutManager}.
+ * Default value is {@code false} (disabled).
+ * This works ONLY in combination with {@link SmoothScrollLinearLayoutManager} or with
+ * {@link SmoothScrollGridLayoutManager}.
*
* @param scrollOnExpand true to enable automatic scroll, false to disable
* @return this Adapter, so the call can be chained
* @since 5.0.0-b1
*/
public FlexibleAdapter setAutoScrollOnExpand(boolean scrollOnExpand) {
+ if (DEBUG) Log.i(TAG, "Set setAutoScrollOnExpand=" + scrollOnExpand);
this.scrollOnExpand = scrollOnExpand;
return this;
}
@@ -1636,23 +2249,24 @@ public boolean isExpandable(@NonNull T item) {
}
/**
- * @return the level of the minium collapsible level used in MultiLevel expandable
+ * @return the level of the minimum collapsible level used in MultiLevel expandable
* @since 5.0.0-b6
*/
public int getMinCollapsibleLevel() {
- return minCollapsibleLevel;
+ return mMinCollapsibleLevel;
}
/**
* Sets the minimum level which all sub expandable items will be collapsed too.
- * Default value is {@link #minCollapsibleLevel} (All levels including 0).
+ * Default value is {@link #mMinCollapsibleLevel} (All levels including 0).
*
* @param minCollapsibleLevel the minimum level to auto-collapse sub expandable items
* @return this Adapter, so the call can be chained
* @since 5.0.0-b6
*/
public FlexibleAdapter setMinCollapsibleLevel(int minCollapsibleLevel) {
- this.minCollapsibleLevel = minCollapsibleLevel;
+ if (DEBUG) Log.i(TAG, "Set minCollapsibleLevel=" + minCollapsibleLevel);
+ this.mMinCollapsibleLevel = minCollapsibleLevel;
return this;
}
@@ -1669,7 +2283,7 @@ public boolean hasSubItems(@NonNull IExpandable expandable) {
}
/**
- * Retrieves the parent of a child.
+ * Retrieves the parent of a child for the provided position.
* Only for a real child of an expanded parent.
*
* @param position the position of the child item
@@ -1687,7 +2301,7 @@ public IExpandable getExpandableOf(@IntRange(from = 0) int position) {
* @param child the child item
* @return the parent of this child item or null if item has no parent
* @see #getExpandablePositionOf(IFlexible)
- * @see #getRelativePositionOf(IFlexible)
+ * @see #getSubPositionOf(IFlexible)
* @since 5.0.0-b1
*/
public IExpandable getExpandableOf(@NonNull T child) {
@@ -1714,7 +2328,7 @@ public IExpandable getExpandableOf(@NonNull T child) {
* @param child the child item
* @return the parent position of this child item or -1 if not found
* @see #getExpandableOf(IFlexible)
- * @see #getRelativePositionOf(IFlexible)
+ * @see #getSubPositionOf(IFlexible)
* @since 5.0.0-b1
*/
public int getExpandablePositionOf(@NonNull T child) {
@@ -1722,34 +2336,50 @@ public int getExpandablePositionOf(@NonNull T child) {
}
/**
- * Provides the list where the child currently lays.
+ * Retrieves the position of a child item in the list where it lays.
+ * Only for a real child of an expanded parent.
*
* @param child the child item
- * @return the list of the child element, or a new list if item
+ * @return the position in the parent or -1 if the child is a parent itself or not found
* @see #getExpandableOf(IFlexible)
* @see #getExpandablePositionOf(IFlexible)
- * @see #getRelativePositionOf(IFlexible)
- * @see #getExpandedItems()
* @since 5.0.0-b1
+ * @deprecated Use {@link #getSubPositionOf(IFlexible)}
*/
- @NonNull
- public List getSiblingsOf(@NonNull T child) {
- IExpandable expandable = getExpandableOf(child);
- return expandable != null ? expandable.getSubItems() : new ArrayList<>();
+ @Deprecated
+ public int getRelativePositionOf(@NonNull T child) {
+ return getSiblingsOf(child).indexOf(child);
+ }
+
+ /**
+ * Retrieves the position of a child item in the list where it lays.
+ * Only for a real child of an expanded parent.
+ *
+ * @param child the child item
+ * @return the position in the parent or -1 if the child is a parent itself or not found
+ * @see #getExpandableOf(IFlexible)
+ * @see #getExpandablePositionOf(IFlexible)
+ * @since 5.0.0-b1
+ */
+ public int getSubPositionOf(@NonNull T child) {
+ return getSiblingsOf(child).indexOf(child);
}
/**
- * Retrieves the position of a child in the list where it lays.
- * Only for a real child of an expanded parent.
+ * Provides the full sub list where the child currently lays.
*
* @param child the child item
- * @return the position in the parent or -1 if, child is a parent itself or not found
+ * @return the list of the child element, or an empty list if the child item has no parent
* @see #getExpandableOf(IFlexible)
* @see #getExpandablePositionOf(IFlexible)
+ * @see #getSubPositionOf(IFlexible)
+ * @see #getExpandedItems()
* @since 5.0.0-b1
*/
- public int getRelativePositionOf(@NonNull T child) {
- return getSiblingsOf(child).indexOf(child);
+ @NonNull
+ public List getSiblingsOf(@NonNull T child) {
+ IExpandable expandable = getExpandableOf(child);
+ return expandable != null ? expandable.getSubItems() : new ArrayList<>();
}
/**
@@ -1781,15 +2411,16 @@ public List getExpandedItems() {
@NonNull
public List getExpandedPositions() {
List expandedPositions = new ArrayList<>();
- for (int i = 0; i < mItems.size() - 1; i++) {
- if (isExpanded(mItems.get(i)))
- expandedPositions.add(i);
+ int startPosition = Math.max(0, mScrollableHeaders.size() - 1);
+ int endPosition = getItemCount() - mScrollableFooters.size() - 1;
+ for (int i = startPosition; i < endPosition; i++) {
+ if (isExpanded(mItems.get(i))) expandedPositions.add(i);
}
return expandedPositions;
}
/**
- * Expands an item that is IExpandable type, not yet expanded and if has subItems.
+ * Expands an item that is {@code IExpandable} type, not yet expanded and if has subItems.
* If configured, automatic smooth scroll will be performed when necessary.
*
* @param position the position of the item to expand
@@ -1826,8 +2457,8 @@ public int expand(T item) {
* initially expanded.
* WARNING!
*
Expanded status is ignored if {@code init = true}: it will always attempt to expand
- * the item: If subItems are already visible and the new item has status expanded, the
- * subItems will appear duplicated and the automatic smooth scroll will be skipped!
+ * the item: If subItems are already visible and the new item has status expanded, the
+ * subItems will appear duplicated(!) and the automatic smooth scroll will be skipped!
*
* @param item the item to expand, must be an Expandable and present in the list
* @param init true to initially expand item
@@ -1853,22 +2484,22 @@ private int expand(int position, boolean expandAll, boolean init) {
" expanded " + expandable.isExpanded());
return 0;
}
-// if (DEBUG && !init) {
-// Log.v(TAG, "Request to Expand on position=" + position +
-// " expanded=" + expandable.isExpanded() +
-// " anyParentSelected=" + parentSelected);
-// }
+ if (DEBUG && !init && !expandAll) {
+ Log.v(TAG, "Request to Expand on position=" + position +
+ " expanded=" + expandable.isExpanded() +
+ " anyParentSelected=" + parentSelected);
+ }
int subItemsCount = 0;
if (init || !expandable.isExpanded() &&
- (!parentSelected || expandable.getExpansionLevel() <= selectedLevel)) {
+ (!parentSelected || expandable.getExpansionLevel() <= mSelectedLevel)) {
- //Collapse others expandable if configured so Skip when expanding all is requested
- //Fetch again the new position after collapsing all!!
- if (collapseOnExpand && !expandAll && collapseAll(minCollapsibleLevel) > 0) {
+ // Collapse others expandable if configured so Skip when expanding all is requested
+ // Fetch again the new position after collapsing all!!
+ if (collapseOnExpand && !expandAll && collapseAll(mMinCollapsibleLevel) > 0) {
position = getGlobalPositionOf(item);
}
- //Every time an expansion is requested, subItems must be taken from the
+ // Every time an expansion is requested, subItems must be taken from the
// original Object and without the subItems marked hidden (removed)
List subItems = getExpandableList(expandable);
mItems.addAll(position + 1, subItems);
@@ -1876,31 +2507,48 @@ private int expand(int position, boolean expandAll, boolean init) {
//Save expanded state
expandable.setExpanded(true);
- //Automatically smooth scroll the current expandable item to show as much
+ // Automatically smooth scroll the current expandable item to show as much
// children as possible
if (!init && scrollOnExpand && !expandAll) {
autoScrollWithDelay(position, subItemsCount, 150L);
}
- //Expand!
+ // Expand!
notifyItemRangeInserted(position + 1, subItemsCount);
- //Show also the headers of the subItems
+ // Show also the headers of the subItems
if (!init && headersShown) {
int count = 0;
for (T subItem : subItems) {
if (showHeaderOf(position + (++count), subItem, false)) count++;
}
}
+
+ // Expandable as a Scrollable Header/Footer
+ if (!expandSHF(mScrollableHeaders, expandable))
+ expandSHF(mScrollableFooters, expandable);
+
if (DEBUG) {
- Log.i(TAG, (init ? "Initially expanded " : "Expanded ") +
+ Log.v(TAG, (init ? "Initially expanded " : "Expanded ") +
subItemsCount + " subItems on position=" + position);
}
}
return subItemsCount;
}
+ private boolean expandSHF(List scrollables, IExpandable expandable) {
+ int index = scrollables.indexOf(expandable);
+ if (index >= 0) {
+ if (index + 1 < scrollables.size()) {
+ return scrollables.addAll(index + 1, expandable.getSubItems());
+ } else {
+ return scrollables.addAll(expandable.getSubItems());
+ }
+ }
+ return false;
+ }
+
/**
- * Expands all IExpandable items with minimum of level {@link #minCollapsibleLevel}.
+ * Expands all IExpandable items with minimum of level {@link #mMinCollapsibleLevel}.
*
* @return the number of parent successfully expanded
* @see #expandAll(int)
@@ -1908,7 +2556,7 @@ private int expand(int position, boolean expandAll, boolean init) {
* @since 5.0.0-b1
*/
public int expandAll() {
- return expandAll(minCollapsibleLevel);
+ return expandAll(mMinCollapsibleLevel);
}
/**
@@ -1922,12 +2570,14 @@ public int expandAll() {
*/
public int expandAll(int level) {
int expanded = 0;
- //More efficient if we expand from First expandable position
- for (int i = 0; i < mItems.size(); i++) {
+ // More efficient if we expand from First expandable position
+ int startPosition = Math.max(0, mScrollableHeaders.size() - 1);
+ for (int i = startPosition; i < (getItemCount() - mScrollableFooters.size()); i++) {
T item = getItem(i);
if (isExpandable(item)) {
IExpandable expandable = (IExpandable) item;
if (expandable.getExpansionLevel() <= level && expand(i, true, false) > 0) {
+ i += expandable.getSubItems().size();
expanded++;
}
}
@@ -1936,8 +2586,8 @@ public int expandAll(int level) {
}
/**
- * Collapses an IExpandable item that is already expanded, if no subItem is selected.
- * Multilevel behaviour: all IExpandable subItem, that are expanded, are recursively
+ * Collapses an {@code IExpandable} item that is already expanded, if no subItem is selected.
+ *
Multilevel behaviour: all {@code IExpandable} subItem, that are expanded, are recursively
* collapsed.
*
* @param position the position of the item to collapse
@@ -1950,11 +2600,11 @@ public int collapse(@IntRange(from = 0) int position) {
if (!isExpandable(item)) return 0;
IExpandable expandable = (IExpandable) item;
- //Take the current subList (will improve the performance when collapseAll)
+ // Take the current subList (will improve the performance when collapseAll)
List subItems = getExpandableList(expandable);
int subItemsCount = subItems.size(), recursiveCount = 0;
- if (DEBUG && hashItems == null) {
+ if (DEBUG && mHashItems == null) {
Log.v(TAG, "Request to Collapse on position=" + position +
" expanded=" + expandable.isExpanded() +
" hasSubItemsSelected=" + hasSubItemsSelected(position, subItems));
@@ -1963,29 +2613,38 @@ public int collapse(@IntRange(from = 0) int position) {
if (expandable.isExpanded() && subItemsCount > 0 &&
(!hasSubItemsSelected(position, subItems) || getPendingRemovedItem(item) != null)) {
- //Recursive collapse of all sub expandable
+ // Recursive collapse of all sub expandable
recursiveCount = recursiveCollapse(position + 1, subItems, expandable.getExpansionLevel());
- //Use HashSet (will improve the performance when collapseAll)
- if (hashItems != null) hashItems.removeAll(subItems);
+ // Use HashSet (will improve the performance when collapseAll)
+ if (mHashItems != null) mHashItems.removeAll(subItems);
else mItems.removeAll(subItems);
subItemsCount = subItems.size();
- //Save expanded state
+ // Save expanded state
expandable.setExpanded(false);
- //Collapse!
+ // Collapse!
notifyItemRangeRemoved(position + 1, subItemsCount);
- //Hide also the headers of the subItems
+ // Hide also the headers of the subItems
if (headersShown && !isHeader(item)) {
for (T subItem : subItems) {
hideHeaderOf(subItem);
}
}
+
+ // Expandable as a Scrollable Header/Footer
+ if (!collapseSHF(mScrollableHeaders, expandable))
+ collapseSHF(mScrollableFooters, expandable);
+
if (DEBUG)
Log.v(TAG, "Collapsed " + subItemsCount + " subItems on position " + position);
}
return subItemsCount + recursiveCount;
}
+ private boolean collapseSHF(List scrollables, IExpandable expandable) {
+ return scrollables.contains(expandable) && scrollables.removeAll(expandable.getSubItems());
+ }
+
private int recursiveCollapse(int startPosition, List subItems, int level) {
int collapsed = 0;
for (int i = 0; i < subItems.size(); i++) {
@@ -2002,7 +2661,7 @@ private int recursiveCollapse(int startPosition, List subItems, int level) {
}
/**
- * Collapses all expandable items with the minimum level of {@link #minCollapsibleLevel}.
+ * Collapses all expandable items with the minimum level of {@link #mMinCollapsibleLevel}.
*
* @return the number of parent successfully collapsed
* @see #collapse(int)
@@ -2011,7 +2670,7 @@ private int recursiveCollapse(int startPosition, List subItems, int level) {
* @since 5.0.0-b1
*/
public int collapseAll() {
- return collapseAll(minCollapsibleLevel);
+ return collapseAll(mMinCollapsibleLevel);
}
/**
@@ -2023,10 +2682,11 @@ public int collapseAll() {
* @since 5.0.0-b6
*/
public int collapseAll(int level) {
- hashItems = new LinkedHashSet<>(mItems);
+ // Use hashSet to increase performance while removing subItems
+ mHashItems = new LinkedHashSet<>(mItems);
int collapsed = recursiveCollapse(0, mItems, level);
- mItems = new ArrayList<>(hashItems);
- hashItems = null;
+ mItems = new ArrayList<>(mHashItems);
+ mHashItems = null;
return collapsed;
}
@@ -2035,7 +2695,7 @@ public int collapseAll(int level) {
/*----------------*/
/**
- * Updates/Rebounds the ItemView corresponding to the current position of that item, with
+ * Updates/Rebounds the itemView corresponding to the current position of that item, with
* the new content provided.
*
* @param item the item with the new content
@@ -2049,7 +2709,7 @@ public void updateItem(@NonNull T item, @Nullable Object payload) {
}
/**
- * Updates/Rebounds the ItemView corresponding to the provided position with the new
+ * Updates/Rebounds the itemView corresponding to the provided position with the new
* provided content. Use {@link #updateItem(IFlexible, Object)} if the new content should
* be bound on the same position.
*
@@ -2062,7 +2722,8 @@ public void updateItem(@NonNull T item, @Nullable Object payload) {
*/
public void updateItem(@IntRange(from = 0) int position, @NonNull T item,
@Nullable Object payload) {
- if (position < 0 || position >= mItems.size()) {
+ int itemCount = getItemCount();
+ if (position < 0 || position >= itemCount) {
Log.e(TAG, "Cannot updateItem on position out of OutOfBounds!");
return;
}
@@ -2076,7 +2737,7 @@ public void updateItem(@IntRange(from = 0) int position, @NonNull T item,
/*----------------*/
/**
- * Inserts the given Item at desired position or Add Item at last position with a delay
+ * Inserts the given item at desired position or Add item at last position with a delay
* and auto-scroll to the position.
* Scrolling animation is automatically preserved, meaning that, notification for animation
* is ignored.
@@ -2098,89 +2759,106 @@ public void addItemWithDelay(@IntRange(from = 0) final int position, @NonNull fi
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- setAnimate(true);
- if (addItem(position, item) && scrollToPosition && mRecyclerView != null) {
- mRecyclerView.smoothScrollToPosition(
- Math.min(Math.max(0, position), getItemCount() - 1));
- }
- setAnimate(false);
+ if (addItem(position, item) && scrollToPosition) performScroll(position);
}
}, delay);
}
/**
- * Inserts the given item in the internal list at the specified position or Adds the item
- * at last position.
+ * Simply append the provided item to the end of the list.
+ * Convenience method of {@link #addItem(int, IFlexible)} with
+ * {@code position = getMainItemCount()}.
+ *
+ * @param item the item to add
+ * @return true if the internal list was successfully modified, false otherwise
+ */
+ public boolean addItem(@NonNull T item) {
+ return addItem(getItemCount(), item);
+ }
+
+ /**
+ * Inserts the given item at the specified position or Adds the item to the end of the list
+ * (no matters if the new position is out of bounds!).
*
- * @param position position of the item to add
+ * @param position position inside the list, if negative, items will be added to the end
* @param item the item to add
* @return true if the internal list was successfully modified, false otherwise
- * @see #addItemWithDelay(int, IFlexible, long, boolean)
+ * @see #addItem(IFlexible)
* @see #addItems(int, List)
* @see #addSubItems(int, int, IExpandable, List, boolean, Object)
+ * @see #addItemWithDelay(int, IFlexible, long, boolean)
* @since 1.0.0
*/
public boolean addItem(@IntRange(from = 0) int position, @NonNull T item) {
if (item == null) {
- Log.e(TAG, "No items to add!");
+ Log.e(TAG, "addItem No items to add!");
return false;
}
if (DEBUG) Log.v(TAG, "addItem delegates addition to addItems!");
- List items = new ArrayList<>(1);
- items.add(item);
- return addItems(position, items);
+ return addItems(position, Collections.singletonList(item));
}
/**
- * Inserts a set of items in the internal list at specified position or Adds the items
- * at last position.
- * NOTE: When all headers are shown, the header (if exists) of this item will be
- * shown as well, unless it's already shown, so it will not be shown twice.
+ * Inserts a set of items at specified position or Adds the items to the end of the list
+ * (no matters if the new position is out of bounds!).
+ * Note:
+ *
- When all headers are shown, if exists, the header of this item will be shown as well,
+ * unless it's already shown, so it won't be shown twice.
+ *
- Items will be always added between any scrollable header and footers.
*
- * @param position position inside the list, -1 to add the set the end of the list
- * @param items the items to add
+ * @param position position inside the list, if negative, items will be added to the end
+ * @param items the set of items to add
* @return true if the internal list was successfully modified, false otherwise
+ * @see #addItem(IFlexible)
* @see #addItem(int, IFlexible)
* @see #addSubItems(int, int, IExpandable, List, boolean, Object)
* @since 5.0.0-b1
*/
public boolean addItems(@IntRange(from = 0) int position, @NonNull List items) {
- if (position < 0) {
- Log.e(TAG, "Cannot addItems on negative position!");
- return false;
- }
if (items == null || items.isEmpty()) {
- Log.e(TAG, "No items to add!");
+ Log.e(TAG, "addItems No items to add!");
return false;
}
- if (DEBUG) Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size());
-
- //Insert Items
- int initialCount = getItemCount();
- if (position < mItems.size()) {
- mItems.addAll(position, items);
- } else {
- mItems.addAll(items);
+ int initialCount = getMainItemCount();//Count only main items!
+ if (position < 0) {
+ Log.w(TAG, "addItems Position is negative! adding items to the end");
+ position = initialCount;
}
- //Notify range addition
- notifyItemRangeInserted(position, items.size());
+ // Insert the item properly
+ performInsert(position, items, true);
- //Show the headers of these items if all headers are already visible
+ // Show the headers of these items if all headers are already visible
if (headersShown && !recursive) {
recursive = true;
for (T item : items)
showHeaderOf(getGlobalPositionOf(item), item, false);//We have to find the correct position!
recursive = false;
}
- //Call listener to update EmptyView
+ // Call listener to update EmptyView
if (!recursive && mUpdateListener != null && !multiRange && initialCount == 0 && getItemCount() > 0)
- mUpdateListener.onUpdateEmptyView(getItemCount());
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
return true;
}
+ private void performInsert(int position, List items, boolean notify) {
+ int itemCount = getItemCount();
+ if (position < itemCount) {
+ mItems.addAll(position, items);
+ } else {
+ mItems.addAll(items);
+ position = itemCount;
+ }
+ // Notify range addition
+ if (notify) {
+ if (DEBUG)
+ Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size());
+ notifyItemRangeInserted(position, items.size());
+ }
+ }
+
/**
* Convenience method of {@link #addSubItem(int, int, IFlexible, boolean, Object)}.
- *
In this case parent item will never be notified nor expanded if it is collapsed.
+ *
In this case parent item will never be expanded if it is collapsed.
*
* @return true if the internal list was successfully modified, false otherwise
* @see #addSubItems(int, int, IExpandable, List, boolean, Object)
@@ -2215,11 +2893,8 @@ public boolean addSubItem(@IntRange(from = 0) int parentPosition,
Log.e(TAG, "No items to add!");
return false;
}
- //Build a new list with 1 item to chain the methods of addSubItems
- List subItems = new ArrayList<>(1);
- subItems.add(item);
- //Reuse the method for subItems
- return addSubItems(parentPosition, subPosition, subItems, expandParent, payload);
+ // Build a new list with 1 item to chain the methods of addSubItems
+ return addSubItems(parentPosition, subPosition, Collections.singletonList(item), expandParent, payload);
}
/**
@@ -2240,7 +2915,10 @@ public boolean addSubItem(@IntRange(from = 0) int parentPosition,
* @return true if the internal list was successfully modified, false otherwise
* @see #addSubItems(int, int, IExpandable, List, boolean, Object)
* @since 5.0.0-b1
+ * @deprecated This is like a command to expand an expandable. We should add items to
+ * expandable ourselves, then call expand!
*/
+ @Deprecated
public int addAllSubItemsFrom(@IntRange(from = 0) int parentPosition,
@NonNull IExpandable parent, boolean expandParent,
@Nullable Object payload) {
@@ -2274,7 +2952,7 @@ public boolean addSubItems(@IntRange(from = 0) int parentPosition,
IExpandable expandable = (IExpandable) parent;
return addSubItems(parentPosition, subPosition, expandable, items, expandParent, payload);
}
- Log.e(TAG, "Passed parentPosition doesn't belong to an Expandable item!");
+ Log.e(TAG, "Provided parentPosition doesn't belong to an Expandable item!");
return false;
}
@@ -2282,9 +2960,9 @@ public boolean addSubItems(@IntRange(from = 0) int parentPosition,
* Adds new subItems on the specified parent item, to the internal list.
* In order to add subItems, the following condition must be satisfied:
*
- The item resulting from the parent position is actually an {@link IExpandable}.
- * Optionally the parent can be expanded and subItems displayed.
- *
Optionally you can pass any payload to notify the parent about the change and optimize
- * the view binding.
+ * Optionally, the parent can be expanded and subItems displayed.
+ *
Optionally, you can pass any payload to notify the parent about the change and
+ * optimize the view binding.
*
* @param parentPosition position of the expandable item that shall contain the subItems
* @param subPosition the start position in the parent where the new items shall be inserted
@@ -2304,16 +2982,16 @@ private boolean addSubItems(@IntRange(from = 0) int parentPosition,
@NonNull IExpandable parent,
@NonNull List items, boolean expandParent, @Nullable Object payload) {
boolean added = false;
- //Expand parent if requested and not already expanded
+ // Expand parent if requested and not already expanded
if (expandParent && !parent.isExpanded()) {
expand(parentPosition);
}
- //Notify the adapter of the new addition to display it and animate it.
- //If parent is collapsed there's no need to notify about the change.
+ // Notify the adapter of the new addition to display it and animate it.
+ // If parent is collapsed there's no need to add sub items.
if (parent.isExpanded()) {
added = addItems(parentPosition + 1 + Math.max(0, subPosition), items);
}
- //Notify the parent about the change if requested
+ // Notify the parent about the change if requested
if (payload != null) notifyItemChanged(parentPosition, payload);
return added;
}
@@ -2350,14 +3028,13 @@ public void addSection(@NonNull IHeader header, @Nullable IHeader refHeader) {
}
/**
- * Adds and shows an empty section.
- * The new section is a {@link IHeader} item and the position is calculated after sorting
- * the Data Set.
- *
- To add Sections to the top, set null the Comparator object or simply call
+ * Adds and shows an empty section. The new section is a {@link IHeader} item and the
+ * position is calculated after sorting the data set.
+ *
- To add Sections to the top, set null the Comparator object or simply call
* {@link #addSection(IHeader)};
*
- To add Sections to the bottom or in the middle, implement a Comparator
* object able to support all the item types this Adapter is displaying or a
- * ClassCastException will be raised.
+ * ClassCastException will be raised.
*
* @param header the section header item to add
* @param comparator the criteria to sort the Data Set used to extract the correct position
@@ -2423,7 +3100,6 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header,
else
addItem(headerPosition + 1 + index, (T) item);
}
- //return the position
return getGlobalPositionOf(item);
}
@@ -2431,6 +3107,51 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header,
/* DELETE ITEMS METHODS */
/*----------------------*/
+ /**
+ * This method clears everything: main items, Scrollable Headers and Footers and
+ * everything else is displayed.
+ *
+ * @see #clearAllBut(Integer...)
+ * @see #removeRange(int, int)
+ * @see #removeItemsOfType(Integer...)
+ * @since 5.0.0-rc1
+ */
+ public void clear() {
+ if (DEBUG) Log.d(TAG, "clearAll views");
+ removeAllScrollableHeaders();
+ removeAllScrollableFooters();
+ removeRange(0, getItemCount(), null);
+ }
+
+ /**
+ * Clears the Adapter list retaining Scrollable Headers and Footers and all items of the
+ * type provided as parameter.
+ * Note:- This method is opposite of {@link #removeItemsOfType(Integer...)}.
+ *
+ * @param viewTypes the viewTypes to retain
+ * @see #clear()
+ * @see #removeItems(List)
+ * @see #removeItemsOfType(Integer...)
+ * @since 5.0.0-rc1
+ */
+ public void clearAllBut(Integer... viewTypes) {
+ List viewTypeList = Arrays.asList(viewTypes);
+ if (viewTypeList.size() > 0) {
+ if (DEBUG) Log.d(TAG, "clearAll retaining views " + viewTypeList);
+ List positionsToRemove = new ArrayList<>();
+ int startPosition = Math.max(0, mScrollableHeaders.size() - 1);
+ int endPosition = getItemCount() - mScrollableFooters.size() - 1;
+ for (int i = startPosition; i < endPosition; i++) {
+ if (!viewTypeList.contains(getItemViewType(i)))
+ positionsToRemove.add(i);
+ }
+ // Remove items by ranges
+ removeItems(positionsToRemove);
+ } else {
+ clear();
+ }
+ }
+
/**
* @deprecated Param {@code resetLayoutAnimation} cannot be used anymore. Simply use
* {@link #removeItemWithDelay(IFlexible, long, boolean)}
@@ -2438,12 +3159,12 @@ public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header,
@Deprecated
public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long delay,
final boolean permanent, boolean resetLayoutAnimation) {
- Log.w(TAG, "Method removeItemWithDelay() with 'resetLayoutAnimation' has been deprecated, param 'resetLayoutAnimation'. Method will be removed with next release!");
+ Log.w(TAG, "Method removeItemWithDelay() with 'resetLayoutAnimation' has been deprecated, param 'resetLayoutAnimation'. Method will be removed with final release!");
removeItemWithDelay(item, delay, permanent);
}
/**
- * Removes the given Item after the given delay.
+ * Removes the given item after the given delay.
* Scrolling animation is automatically preserved, meaning that notification for animation
* is ignored.
*
@@ -2463,18 +3184,21 @@ public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- setAnimate(true);
- boolean tempPermanent = permanentDelete;
- if (permanent) permanentDelete = true;
- removeItem(getGlobalPositionOf(item));
- permanentDelete = tempPermanent;
- setAnimate(false);
+ performRemove(item, permanent);
}
}, delay);
}
+ private void performRemove(T item, boolean permanent) {
+ boolean tempPermanent = permanentDelete;
+ if (permanent) permanentDelete = true;
+ removeItem(getGlobalPositionOf(item));
+ permanentDelete = tempPermanent;
+ }
+
/**
- * Convenience method of {@link #removeItem(int, Object)} providing a null payload.
+ * Convenience method of {@link #removeItem(int, Object)} providing {@link Payload#CHANGE}
+ * as payload for the parent item.
*
* @param position the position of item to remove
* @see #removeItems(List)
@@ -2505,14 +3229,15 @@ public void removeItem(@IntRange(from = 0) int position) {
* @since 5.0.0-b1
*/
public void removeItem(@IntRange(from = 0) int position, @Nullable Object payload) {
- //Request to collapse after the notification of remove range
+ // Request to collapse after the notification of remove range
collapse(position);
if (DEBUG) Log.v(TAG, "removeItem delegates removal to removeRange");
removeRange(position, 1, payload);
}
/**
- * Convenience method of {@link #removeItems(List, Object)} providing a null payload.
+ * Convenience method of {@link #removeItems(List, Object)} providing the default
+ * {@link Payload#CHANGE} for the parent items.
*
* @see #removeItem(int)
* @see #removeItemsOfType(Integer...)
@@ -2526,10 +3251,10 @@ public void removeItems(@NonNull List selectedPositions) {
}
/**
- * Removes a list of items from internal list and notify the change.
+ * Removes items by ranges and notify the change.
* Every item is retained for an eventual Undo.
- * Optionally you can pass any payload to notify the parent about the change and optimize the
- * view binding.
+ * Optionally you can pass any payload to notify the parent items about the change to
+ * optimize the view binding.
* This method delegates the removal to removeRange.
*
* @param selectedPositions list with item positions to remove
@@ -2545,9 +3270,9 @@ public void removeItems(@NonNull List selectedPositions) {
public void removeItems(@NonNull List selectedPositions, @Nullable Object payload) {
if (DEBUG)
Log.v(TAG, "removeItems selectedPositions=" + selectedPositions + " payload=" + payload);
- //Check if list is empty
+ // Check if list is empty
if (selectedPositions == null || selectedPositions.isEmpty()) return;
- //Reverse-sort the list, start from last position for efficiency
+ // Reverse-sort the list, start from last position for efficiency
Collections.sort(selectedPositions, new Comparator() {
@Override
public int compare(Integer lhs, Integer rhs) {
@@ -2556,7 +3281,7 @@ public int compare(Integer lhs, Integer rhs) {
});
if (DEBUG)
Log.v(TAG, "removeItems after reverse sort selectedPositions=" + selectedPositions);
- //Split the list in ranges
+ // Split the list in ranges
int positionStart = 0, itemCount = 0;
int lastPosition = selectedPositions.get(0);
multiRange = true;
@@ -2565,17 +3290,17 @@ public int compare(Integer lhs, Integer rhs) {
itemCount++; // 1 2 3 //2
positionStart = position;//10 9 8 //4
} else {
- //Remove range
+ // Remove range
if (itemCount > 0)
removeRange(positionStart, itemCount, payload);//8,3 //4,2
positionStart = lastPosition = position;//5 //1
itemCount = 1;
}
- //Request to collapse after the notification of remove range
+ // Request to collapse after the notification of remove range
collapse(position);
}
multiRange = false;
- //Remove last range
+ // Remove last range
if (itemCount > 0) {
removeRange(positionStart, itemCount, payload);//1,1
}
@@ -2583,8 +3308,13 @@ public int compare(Integer lhs, Integer rhs) {
/**
* Selectively removes all items of the type provided as parameter.
+ * Note:
+ *
- This method is opposite of {@link #clearAllBut(Integer...)}.
+ *
- View types of Scrollable Headers and Footers are ignored!
*
* @param viewTypes the viewTypes to remove
+ * @see #clear()
+ * @see #clearAllBut(Integer...)
* @see #removeItem(int, Object)
* @see #removeItems(List)
* @see #removeAllSelectedItems()
@@ -2593,10 +3323,10 @@ public int compare(Integer lhs, Integer rhs) {
public void removeItemsOfType(Integer... viewTypes) {
List viewTypeList = Arrays.asList(viewTypes);
List itemsToRemove = new ArrayList<>();
- for (int i = mItems.size() - 1; i >= 0; i--) {
- //Privilege autoMap if active
- if ((autoMap && viewTypeList.contains(mItems.get(i).getLayoutRes())) ||
- viewTypeList.contains(getItemViewType(i)))
+ int startPosition = Math.max(0, mScrollableHeaders.size() - 1);
+ int endPosition = getItemCount() - mScrollableFooters.size() - 1;
+ for (int i = endPosition; i >= startPosition; i--) {
+ if (viewTypeList.contains(getItemViewType(i)))
itemsToRemove.add(i);
}
this.removeItems(itemsToRemove);
@@ -2606,6 +3336,8 @@ public void removeItemsOfType(Integer... viewTypes) {
* Same as {@link #removeRange(int, int, Object)}, but in this case the parent will not be
* notified about the change, if children are removed.
*
+ * @see #clear()
+ * @see #clearAllBut(Integer...)
* @see #removeItem(int, Object)
* @see #removeItems(List)
* @see #removeItemsOfType(Integer...)
@@ -2619,8 +3351,8 @@ public void removeRange(@IntRange(from = 0) int positionStart,
}
/**
- * Removes a list of consecutive items from internal list and notify the change.
- * If the item, resulting from the passed position:
+ * Removes a list of consecutive items and notify the change.
+ * If the item, resulting from the provided position:
* - is not expandable with no parent, it is removed as usual.
* - is not expandable with a parent, it is removed only if the parent is expanded.
* - is expandable implementing {@link IExpandable}, it is removed as usual, but
@@ -2635,12 +3367,13 @@ public void removeRange(@IntRange(from = 0) int positionStart,
* @param payload any non-null user object to notify the parent (the payload will be
* therefore passed to the bind method of the parent ViewHolder),
* pass null to not notify the parent
+ * @see #clear()
+ * @see #clearAllBut(Integer...)
* @see #removeItem(int, Object)
* @see #removeItems(List, Object)
* @see #removeRange(int, int)
* @see #removeAllSelectedItems(Object)
* @see #setPermanentDelete(boolean)
- * @see #setRemoveOrphanHeaders(boolean)
* @see #setRestoreSelectionOnUndo(boolean)
* @see #getDeletedItems()
* @see #getDeletedChildren(IExpandable)
@@ -2649,6 +3382,7 @@ public void removeRange(@IntRange(from = 0) int positionStart,
* @see #emptyBin()
* @since 5.0.0-b1
*/
+ //FIXME: Fix payload message if customized
public void removeRange(@IntRange(from = 0) int positionStart,
@IntRange(from = 0) int itemCount, @Nullable Object payload) {
int initialCount = getItemCount();
@@ -2659,11 +3393,12 @@ public void removeRange(@IntRange(from = 0) int positionStart,
return;
}
- //Handle header linkage
- IHeader header = getHeaderOf(getItem(positionStart));
+ // Update content of the header linked to first item of the range
+ T item = getItem(positionStart);
+ IHeader header = getHeaderOf(item);
int headerPosition = getGlobalPositionOf(header);
if (header != null && headerPosition >= 0) {
- //The header does not represents a group anymore, add it to the Orphan list
+ // The header does not represents a group anymore, add it to the Orphan list
addToOrphanListIfNeeded(header, positionStart, itemCount);
notifyItemChanged(headerPosition, payload);
}
@@ -2671,7 +3406,7 @@ public void removeRange(@IntRange(from = 0) int positionStart,
int parentPosition = -1;
IExpandable parent = null;
for (int position = positionStart; position < positionStart + itemCount; position++) {
- T item = getItem(positionStart);
+ item = getItem(positionStart);
if (!permanentDelete) {
//When removing a range of children, parent is always the same :-)
if (parent == null) parent = getExpandableOf(item);
@@ -2682,37 +3417,33 @@ public void removeRange(@IntRange(from = 0) int positionStart,
parentPosition = createRestoreSubItemInfo(parent, item, Payload.UNDO);
}
}
- //Change to hidden status for section headers
+ // Change to hidden status for section headers
if (isHeader(item)) {
header = (IHeader) item;
header.setHidden(true);
- }
- //If item is a Header, remove linkage from ALL Sectionable items if exist
- if (unlinkOnRemoveHeader && isHeader(item)) {
- List sectionableList = getSectionItems(header);
- for (ISectionable sectionable : sectionableList) {
- sectionable.setHeader(null);
- if (payload != null)
- notifyItemChanged(getGlobalPositionOf(sectionable), Payload.UNLINK);
+ // If item is a Header, remove linkage from ALL Sectionable items if exist
+ if (unlinkOnRemoveHeader) {
+ List sectionableList = getSectionItems(header);
+ for (ISectionable sectionable : sectionableList) {
+ sectionable.setHeader(null);
+ if (payload != null)
+ notifyItemChanged(getGlobalPositionOf(sectionable), Payload.UNLINK);
+ }
}
}
- //Remove item from internal list
+ // Remove item from internal list
mItems.remove(positionStart);
removeSelection(position);
}
- //Notify removals
- if (parentPosition >= 0) {
- //Notify the Children removal only if Parent is expanded
- notifyItemRangeRemoved(positionStart, itemCount);
- //Notify the Parent about the change if requested
- if (payload != null) notifyItemChanged(parentPosition, payload);
- } else {
- //Notify range removal
- notifyItemRangeRemoved(positionStart, itemCount);
+ // Notify range removal
+ notifyItemRangeRemoved(positionStart, itemCount);
+ // Notify the Parent about the change if requested
+ if (parentPosition >= 0 && payload != null) {
+ notifyItemChanged(parentPosition, payload);
}
- //Remove orphan headers
+ // Remove orphan headers
if (removeOrphanHeaders) {
for (IHeader orphanHeader : mOrphanHeaders) {
headerPosition = getGlobalPositionOf(orphanHeader);
@@ -2727,15 +3458,17 @@ public void removeRange(@IntRange(from = 0) int positionStart,
mOrphanHeaders.clear();
}
- //Update empty view
+ // Update empty view
if (mUpdateListener != null && !multiRange && initialCount > 0 && getItemCount() == 0)
- mUpdateListener.onUpdateEmptyView(getItemCount());
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
}
/**
- * Convenience method to remove all Items that are currently selected.
- * Parent will not be notified about the change, if a child is removed.
+ * Convenience method to remove all items that are currently selected.
+ * Parent will not be notified about the change if a child is removed.
*
+ * @see #clear()
+ * @see #clearAllBut(Integer...)
* @see #removeItem(int)
* @see #removeItems(List)
* @see #removeRange(int, int)
@@ -2748,13 +3481,15 @@ public void removeAllSelectedItems() {
}
/**
- * Convenience method to remove all Items that are currently selected.
- * Optionally the Parent can be notified about the change, if a child is removed, by passing
- * any payload.
+ * Convenience method to remove all items that are currently selected.
+ *
Optionally, the parent item can be notified about the change if a child is removed,
+ * by providing any non-null payload.
*
* @param payload any non-null user object to notify the parent (the payload will be
* therefore passed to the bind method of the parent ViewHolder),
* pass null to not notify the parent
+ * @see #clear()
+ * @see #clearAllBut(Integer...)
* @see #removeItem(int, Object)
* @see #removeItems(List, Object)
* @see #removeRange(int, int, Object)
@@ -2791,6 +3526,7 @@ public boolean isPermanentDelete() {
* @since 5.0.0-b6
*/
public FlexibleAdapter setPermanentDelete(boolean permanentDelete) {
+ if (DEBUG) Log.i(TAG, "Set permanentDelete=" + permanentDelete);
this.permanentDelete = permanentDelete;
return this;
}
@@ -2818,13 +3554,14 @@ public boolean isRestoreWithSelection() {
* @since 5.0.0-b1
*/
public FlexibleAdapter setRestoreSelectionOnUndo(boolean restoreSelection) {
+ if (DEBUG) Log.i(TAG, "Set restoreSelectionOnUndo=" + restoreSelection);
this.restoreSelection = restoreSelection;
return this;
}
/**
* Restore items just removed.
- * NOTE: If filter is active, only items that match that filter will be shown(restored).
+ * Note: If filter is active, only items that match that filter will be shown(restored).
*
* @see #setRestoreSelectionOnUndo(boolean)
* @since 3.0.0
@@ -2834,53 +3571,53 @@ public void restoreDeletedItems() {
stopUndoTimer();
multiRange = true;
int initialCount = getItemCount();
- //Selection coherence: start from a clear situation
+ // Selection coherence: start from a clear situation
clearSelection();
- //Start from latest item deleted, since others could rely on it
+ // Start from latest item deleted, since others could rely on it
for (int i = mRestoreList.size() - 1; i >= 0; i--) {
adjustSelected = false;
RestoreInfo restoreInfo = mRestoreList.get(i);
- //Notify header if exists
+ // Notify header if exists
IHeader header = getHeaderOf(restoreInfo.item);
if (header != null) {
notifyItemChanged(getGlobalPositionOf(header), restoreInfo.payload);
}
if (restoreInfo.relativePosition >= 0) {
- //Restore child, if not deleted
+ // Restore child, if not deleted
if (DEBUG) Log.d(TAG, "Restore Child " + restoreInfo);
- //Skip subItem addition if filter is active
+ // Skip subItem addition if filter is active
if (hasSearchText() && !filterObject(restoreInfo.item, getSearchText()))
continue;
- //Check if refItem is shown, if not, show it again
+ // Check if refItem is shown, if not, show it again
if (hasSearchText() &&
getGlobalPositionOf(getHeaderOf(restoreInfo.item)) == RecyclerView.NO_POSITION) {
- //Add parent + subItem
+ // Add parent + subItem
restoreInfo.refItem.setHidden(false);
addItem(restoreInfo.getRestorePosition(false), restoreInfo.refItem);
addSubItem(restoreInfo.getRestorePosition(true), 0, restoreInfo.item, true, restoreInfo.payload);
} else {
- //Add subItem
+ // Add subItem
addSubItem(restoreInfo.getRestorePosition(true), restoreInfo.relativePosition,
restoreInfo.item, false, restoreInfo.payload);
}
} else {
- //Restore parent or simple item, if not deleted
+ // Restore parent or simple item, if not deleted
if (DEBUG) Log.d(TAG, "Restore Parent " + restoreInfo);
- //Skip item addition if filter is active
+ // Skip item addition if filter is active
if (hasSearchText() && !filterExpandableObject(restoreInfo.item))
continue;
- //Add header if not visible
+ // Add header if not visible
if (hasSearchText() && hasHeader(restoreInfo.item) &&
getGlobalPositionOf(getHeaderOf(restoreInfo.item)) == RecyclerView.NO_POSITION)
getHeaderOf(restoreInfo.item).setHidden(true);
- //Add item
+ // Add item
addItem(restoreInfo.getRestorePosition(false), restoreInfo.item);
}
- //Item is again visible
+ // Item is again visible
restoreInfo.item.setHidden(false);
- //Restore header linkage
+ // Restore header linkage
if (unlinkOnRemoveHeader && isHeader(restoreInfo.item)) {
header = (IHeader) restoreInfo.item;
List items = getSectionItems(header);
@@ -2889,7 +3626,7 @@ public void restoreDeletedItems() {
}
}
}
- //Restore selection if requested, before emptyBin
+ // Restore selection if requested, before emptyBin
if (restoreSelection && !mRestoreList.isEmpty()) {
if (isExpandable(mRestoreList.get(0).item) || getExpandableOf(mRestoreList.get(0).item) == null) {
parentSelected = true;
@@ -2904,16 +3641,16 @@ public void restoreDeletedItems() {
if (DEBUG) Log.d(TAG, "Selected positions after restore " + getSelectedPositions());
}
- //Call listener to update EmptyView
+ // Call listener to update EmptyView
multiRange = false;
if (mUpdateListener != null && initialCount == 0 && getItemCount() > 0)
- mUpdateListener.onUpdateEmptyView(getItemCount());
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
emptyBin();
}
/**
- * Clean memory from items just removed.
+ * Cleans memory from items just removed.
* Note: This method is automatically called after timer is over and after a
* restoration.
*
@@ -2925,7 +3662,7 @@ public synchronized void emptyBin() {
}
/**
- * Convenience method to start Undo timer with default timeout of 5''
+ * Convenience method to start Undo timer with default timeout of 5''.
*
* @param listener the listener that will be called after timeout to commit the change
* @since 3.0.0
@@ -2935,14 +3672,14 @@ public void startUndoTimer(OnDeleteCompleteListener listener) {
}
/**
- * Start Undo timer with custom timeout
+ * Start Undo timer with custom timeout.
*
* @param timeout custom timeout
* @param listener the listener that will be called after timeout to commit the change
* @since 3.0.0
*/
public void startUndoTimer(long timeout, OnDeleteCompleteListener listener) {
- //Make longer the timer for new coming deleted items
+ // Make longer the timer for new coming deleted items
stopUndoTimer();
mHandler.sendMessageDelayed(Message.obtain(mHandler, CONFIRM_DELETE, listener), timeout > 0 ? timeout : UNDO_TIMEOUT);
}
@@ -2961,7 +3698,7 @@ protected void stopUndoTimer() {
* @return true if the restore list is not empty, false otherwise
* @since 4.0.0
*/
- public boolean isRestoreInTime() {
+ public final boolean isRestoreInTime() {
return mRestoreList != null && !mRestoreList.isEmpty();
}
@@ -2985,7 +3722,7 @@ public List getDeletedItems() {
* @return the expandable(parent) of this child, or null if no parent found.
* @since 5.0.0-b1
*/
- public IExpandable getExpandableOfDeletedChild(T child) {
+ public final IExpandable getExpandableOfDeletedChild(T child) {
for (RestoreInfo restoreInfo : mRestoreList) {
if (restoreInfo.item.equals(child) && isExpandable(restoreInfo.refItem))
return (IExpandable) restoreInfo.refItem;
@@ -3001,7 +3738,7 @@ public IExpandable getExpandableOfDeletedChild(T child) {
* @since 5.0.0-b1
*/
@NonNull
- public List getDeletedChildren(IExpandable expandable) {
+ public final List getDeletedChildren(IExpandable expandable) {
List deletedChild = new ArrayList<>();
for (RestoreInfo restoreInfo : mRestoreList) {
if (restoreInfo.refItem != null && restoreInfo.refItem.equals(expandable) && restoreInfo.relativePosition >= 0)
@@ -3020,14 +3757,14 @@ public List getDeletedChildren(IExpandable expandable) {
* @since 5.0.0-b1
*/
@NonNull
- public List getCurrentChildren(@NonNull IExpandable expandable) {
- //Check item and subItems existence
+ public final List getCurrentChildren(@NonNull IExpandable expandable) {
+ // Check item and subItems existence
if (expandable == null || !hasSubItems(expandable))
return new ArrayList<>();
- //Take a copy of the subItems list
+ // Take a copy of the subItems list
List subItems = new ArrayList<>(expandable.getSubItems());
- //Remove all children pending removal
+ // Remove all children pending removal
if (!mRestoreList.isEmpty()) {
subItems.removeAll(getDeletedChildren(expandable));
}
@@ -3067,6 +3804,7 @@ public String getSearchText() {
/**
* Sets the new search text.
+ * Note: text is always trimmed and to lowercase.
*
* @param searchText the new text to filter the items
* @since 3.1.0
@@ -3083,7 +3821,7 @@ public void setSearchText(String searchText) {
* If the items have highlighted text, those items must be refreshed in order to change the
* text back to normal. This happens systematically when searchText is reduced in length by
* the user.
- * The notification is triggered in {@link #animateTo(List)} when new items are not added.
+ * The notification is triggered in {@link #animateTo(List, Payload)} when new items are not added.
* Default value is {@code false}.
*
* @param notifyChange true to trigger {@link #notifyItemChanged(int)} while filtering,
@@ -3092,6 +3830,7 @@ public void setSearchText(String searchText) {
* @since 5.0.0-b1
*/
public final FlexibleAdapter setNotifyChangeOfUnfilteredItems(boolean notifyChange) {
+ if (DEBUG) Log.i(TAG, "Set notifyChangeOfUnfilteredItems=" + notifyChange);
this.notifyChangeOfUnfilteredItems = notifyChange;
return this;
}
@@ -3110,6 +3849,7 @@ public final FlexibleAdapter setNotifyChangeOfUnfilteredItems(boolean notifyChan
* @since 5.0.0-b8
*/
public final FlexibleAdapter setNotifyMoveOfFilteredItems(boolean notifyMove) {
+ if (DEBUG) Log.i(TAG, "Set notifyMoveOfFilteredItems=" + notifyMove);
this.notifyMoveOfFilteredItems = notifyMove;
return this;
}
@@ -3123,6 +3863,7 @@ public final FlexibleAdapter setNotifyMoveOfFilteredItems(boolean notifyMove) {
* @param unfilteredItems the list to filter
* @param delay any non-negative delay
* @see #filterObject(IFlexible, String)
+ * @see #onPostFilter()
* @see #setAnimateToLimit(int)
* @since 5.0.0-b1
*
5.0.0-b8 Synchronization animations limit + AsyncFilter
@@ -3136,28 +3877,33 @@ public void filterItems(@NonNull List unfilteredItems, @IntRange(from = 0) lo
/**
* WATCH OUT! PASS ALWAYS A COPY OF THE ORIGINAL LIST: due to internal
* mechanism, items are removed and/or added in order to animate items in the final list.
- * This method filters the provided list with the search text previously set with
+ *
This method filters the provided list with the searchText previously set with
* {@link #setSearchText(String)}.
* Important notes:
*
+ * - The Filter is always executed in background, asynchronously.
+ * The method {@link #onPostFilter()} is called after the filter task is completed.
* - This method calls {@link #filterObject(IFlexible, String)}.
- * - If search text is empty or null, the provided list is the current list.
+ * - If searchText is empty or {@code null}, the provided list is the new list plus any
+ * Scrollable Headers and Footers if existent.
* - Any pending deleted items are always filtered out, but if restored, they will be
* displayed according to the current filter and at the right positions.
- * - NEW! Expandable items are picked up and displayed if at least a child is
- * collected by the current filter.
- * - NEW! Items are animated thanks to {@link #animateTo(List)} BUT a limit of
- * {@value mAnimateToLimit} (default) items is set. NOTE: you can change this limit
- * by calling {@link #setAnimateToLimit(int)}. Above this limit {@link #notifyDataSetChanged()}
- * will be called to improve performance.
+ * - Expandable items are picked up and displayed if at least a child is collected by
+ * the current filter.
+ * - Items are animated thanks to {@link #animateTo(List, Payload)} BUT a limit of
+ * {@value ANIMATE_TO_LIMIT} (default) items is set. Note: Above this limit,
+ * {@link #notifyDataSetChanged()} will be called to improve performance. you can change
+ * this limit by calling {@link #setAnimateToLimit(int)}.
*
*
* @param unfilteredItems the list to filter
* @see #filterObject(IFlexible, String)
+ * @see #onPostFilter()
* @see #setAnimateToLimit(int)
* @since 4.1.0 Created
*
5.0.0-b1 Expandable + Child filtering
*
5.0.0-b8 Synchronization animations limit + AsyncFilter
+ *
5.0.0-rc1 Scrollable Headers and Footers adaptation
*/
public void filterItems(@NonNull List unfilteredItems) {
mHandler.removeMessages(FILTER);
@@ -3165,19 +3911,19 @@ public void filterItems(@NonNull List unfilteredItems) {
}
private synchronized void filterItemsAsync(@NonNull List unfilteredItems) {
- // NOTE: In case user has deleted some items and he changes or applies a filter while
+ // Note: In case user has deleted some items and he changes or applies a filter while
// deletion is pending (Undo started), in order to be consistent, we need to recalculate
// the new position in the new list and finally skip those items to avoid they are shown!
- if (DEBUG) Log.i(TAG, "filterItems with searchText=" + mSearchText);
+ if (DEBUG) Log.i(TAG, "filterItems with searchText=\"" + mSearchText + "\"");
List filteredItems = new ArrayList<>();
- filtering = true;//Enable flag: skip adjustPositions!
+ filtering = true; //Enable flag: skip adjustPositions!
- if (hasSearchText()) {
+ if (hasSearchText() && hasNewSearchText(mSearchText)) { //skip when text is unchanged
int newOriginalPosition = -1;
for (T item : unfilteredItems) {
if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled()) return;
- //Filter header first
+ // Filter header first
T header = (T) getHeaderOf(item);
if (headersShown) {
if (header != null && filterObject(header, getSearchText())
@@ -3188,7 +3934,7 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) {
if (filterExpandableObject(item)) {
RestoreInfo restoreInfo = getPendingRemovedItem(item);
if (restoreInfo != null) {
- //If found, point to the new reference while filtering
+ // If found, point to the new reference while filtering
restoreInfo.filterRefItem = ++newOriginalPosition < filteredItems.size() ?
filteredItems.get(newOriginalPosition) : null;
} else {
@@ -3202,29 +3948,31 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) {
item.setHidden(true);
}
}
- } else if (hasNewSearchText(mSearchText)) {//this is better than checking emptiness
+ } else if (hasNewSearchText(mSearchText)) { //this is better than checking emptiness
filteredItems = unfilteredItems; //with no filter
if (!mRestoreList.isEmpty()) {
for (RestoreInfo restoreInfo : mRestoreList) {
- //Clear the refItem generated by the filter
+ // Clear the refItem generated by the filter
restoreInfo.clearFilterRef();
- //Find the real reference
+ // Find the real reference
restoreInfo.refItem = filteredItems.get(
Math.max(0, filteredItems.indexOf(restoreInfo.item) - 1));
}
- //Deleted items not yet committed should not appear
+ // Deleted items not yet committed should not appear
filteredItems.removeAll(getDeletedItems());
}
resetFilterFlags(filteredItems);
+ restoreScrollableHeadersAndFooters(filteredItems);
}
- //Reset flags
+ // Reset flags
filtering = false;
- //Animate search results only in case of new SearchText
+ // Animate search results only in case of new SearchText
if (hasNewSearchText(mSearchText)) {
mOldSearchText = mSearchText;
- animateTo(filteredItems);
+ animateDiff(filteredItems, Payload.FILTER);
+ //animateTo(filteredItems, Payload.FILTER);
}
}
@@ -3240,36 +3988,36 @@ private synchronized void filterItemsAsync(@NonNull List unfilteredItems) {
* @since 5.0.0-b1
*/
private boolean filterExpandableObject(T item) {
- //Reset expansion flag
+ // Reset expansion flag
boolean filtered = false;
if (isExpandable(item)) {
IExpandable expandable = (IExpandable) item;
- //Save which expandable was originally expanded before filtering it out
+ // Save which expandable was originally expanded before filtering it out
if (expandable.isExpanded()) {
if (mExpandedFilterFlags == null)
mExpandedFilterFlags = new HashSet<>();
mExpandedFilterFlags.add(expandable);
}
expandable.setExpanded(false);
- //Children scan filter
+ // Children scan filter
for (T subItem : getCurrentChildren(expandable)) {
- //Reuse normal filter for Children
+ // Reuse normal filter for Children
subItem.setHidden(!filterObject(subItem, getSearchText()));
if (!filtered && !subItem.isHidden()) {
filtered = true;
}
}
- //Expand if filter found text in subItems
+ // Expand if filter found text in subItems
expandable.setExpanded(filtered);
}
- //if not filtered already, fallback to Normal filter
+ // if not filtered already, fallback to Normal filter
return filtered || filterObject(item, getSearchText());
}
/**
* This method checks if the provided object is a type of {@link IFilterable} interface,
* if yes, performs the filter on the implemented method {@link IFilterable#filter(String)}.
- * NOTE:
+ *
Note:
*
- The item will be collected if the implemented method returns true.
*
- {@code IExpandable} items are automatically picked up and displayed if at least a
* child is collected by the current filter. You DON'T NEED to implement the scan for the
@@ -3285,11 +4033,7 @@ private boolean filterExpandableObject(T item) {
*
5.0.0-b1 Expandable + Child filtering
*/
protected boolean filterObject(T item, String constraint) {
- if (item instanceof IFilterable) {
- IFilterable filterable = (IFilterable) item;
- return filterable.filter(constraint);
- }
- return false;
+ return item instanceof IFilterable && ((IFilterable) item).filter(constraint);
}
/**
@@ -3299,7 +4043,7 @@ private int addFilteredSubItems(List values, T item) {
if (isExpandable(item)) {
IExpandable expandable = (IExpandable) item;
if (hasSubItems(expandable)) {
- //Add subItems if not hidden by filterObject()
+ // Add subItems if not hidden by filterObject()
List filteredSubItems = new ArrayList<>();
List subItems = expandable.getSubItems();
for (T subItem : subItems) {
@@ -3316,21 +4060,21 @@ private int addFilteredSubItems(List values, T item) {
* Clears flags after searchText is cleared out for Expandable items and sub items.
*/
private void resetFilterFlags(List items) {
- //Reset flags for all items!
+ // Reset flags for all items!
for (int i = 0; i < items.size(); i++) {
T item = items.get(i);
item.setHidden(false);
if (isExpandable(item)) {
IExpandable expandable = (IExpandable) item;
- //Reset expanded flag
+ // Reset expanded flag
if (mExpandedFilterFlags != null)
expandable.setExpanded(mExpandedFilterFlags.contains(expandable));
if (hasSubItems(expandable)) {
List subItems = expandable.getSubItems();
for (T subItem : subItems) {
- //Reset subItem hidden flag
+ // Reset subItem hidden flag
subItem.setHidden(false);
- //Show subItems for expanded items
+ // Show subItems for expanded items
if (expandable.isExpanded()) {
i++;
if (i < items.size()) items.add(i, subItem);
@@ -3347,24 +4091,90 @@ private void resetFilterFlags(List items) {
* Tunes the limit after the which the synchronization animations, occurred during
* updateDataSet and filter operations, are skipped and {@link #notifyDataSetChanged()}
* will be called instead.
- * Default value is {@value mAnimateToLimit} items, number new items.
+ * Default value is {@value ANIMATE_TO_LIMIT} items, number new items.
*
* @param limit the number of new items that, when reached, will skip synchronization animations
* @return this Adapter, so the call can be chained
* @since 5.0.0-b8
*/
public FlexibleAdapter setAnimateToLimit(int limit) {
+ if (DEBUG) Log.i(TAG, "Set animateToLimit=" + limit);
mAnimateToLimit = limit;
return this;
}
+ /*-------------------------*/
+ /* ANIMATE CHANGES METHODS */
+ /*-------------------------*/
+
+ /**
+ * @return true to calculate animation changes with DiffUtil, false to use default calculation.
+ * @see #setAnimateChangesWithDiffUtil(boolean)
+ * @deprecated DiffUtil is slower than the internal implementation, you can use it until
+ * final release.
+ */
+ @Deprecated
+ public boolean isAnimateChangesWithDiffUtil() {
+ return useDiffUtil;
+ }
+
+ /**
+ * Whether use {@link DiffUtil} to calculate the changes between 2 lists after Update or
+ * Filter operations. If disabled, the advanced default calculation will be used instead.
+ * A time, to compare the 2 different approaches, is calculated and displayed in the log.
+ * To see the logs call {@link #enableLogs(boolean)} before creating the Adapter instance!
+ * Default value is {@code false} (default calculation is used).
+ *
+ * @param useDiffUtil true to switch the calculation and use DiffUtil, false to use the default
+ * calculation.
+ * @return this Adapter, so the call can be chained
+ * @see #setDiffUtilCallback(DiffUtilCallback)
+ * @deprecated DiffUtil is slower than the internal implementation, you can use it until
+ * final release.
+ */
+ @Deprecated
+ public FlexibleAdapter setAnimateChangesWithDiffUtil(boolean useDiffUtil) {
+ this.useDiffUtil = useDiffUtil;
+ return this;
+ }
+
+ /**
+ * Sets a custom implementation of {@link DiffUtilCallback} for the DiffUtil. Extend to
+ * implement the comparing methods.
+ *
+ * @param diffUtilCallback the custom callback that DiffUtil will call
+ * @return this Adapter, so the call can be chained
+ * @see #setAnimateChangesWithDiffUtil(boolean)
+ * @deprecated DiffUtil is slower than the internal implementation, you can use it until
+ * final release.
+ */
+ @Deprecated
+ public FlexibleAdapter setDiffUtilCallback(DiffUtilCallback diffUtilCallback) {
+ this.diffUtilCallback = diffUtilCallback;
+ return this;
+ }
+
+ @Deprecated //TODO: Call animateTo instead.
+ private synchronized void animateDiff(@Nullable List newItems, Payload payloadChange) {
+ if (useDiffUtil) {
+ Log.v(TAG, "Animate changes with DiffUtils! oldSize=" + getItemCount() + " newSize=" + newItems.size());
+ if (diffUtilCallback == null) {
+ diffUtilCallback = new DiffUtilCallback();
+ }
+ diffUtilCallback.setItems(mItems, newItems);
+ diffResult = DiffUtil.calculateDiff(diffUtilCallback, notifyMoveOfFilteredItems);
+ } else {
+ animateTo(newItems, payloadChange);
+ }
+ }
+
/**
* Animate the synchronization between the old list and the new list.
* Used by filter and updateDataSet.
- * Note: The animations are skipped in favor of {@code notifyDataSetChanged}
+ * Note: The animations are skipped in favor of {@link #notifyDataSetChanged()}
* when the number of items reaches the limit. See {@link #setAnimateToLimit(int)}.
* Note: In case the animations are performed, unchanged items will be notified if
- * {@code notifyChangeOfUnfilteredItems} is set true, and payload will be set as a Boolean.
+ * {@code notifyChangeOfUnfilteredItems} is set true, and CHANGE payload will be set.
*
* @param newItems the new list containing the new items
* @see #setNotifyChangeOfUnfilteredItems(boolean)
@@ -3373,9 +4183,8 @@ public FlexibleAdapter setAnimateToLimit(int limit) {
* @since 5.0.0-b1 Created
*
5.0.0-b8 Synchronization animation limit
*/
- private synchronized void animateTo(@Nullable List newItems) {
- if (newItems == null) newItems = new ArrayList<>();
- notifications = new ArrayList<>();
+ private synchronized void animateTo(@Nullable List newItems, Payload payloadChange) {
+ mNotifications = new ArrayList<>();
if (newItems.size() <= mAnimateToLimit) {
if (DEBUG)
Log.v(TAG, "Animate changes! oldSize=" + getItemCount() + " newSize=" + newItems.size() + " limit=" + mAnimateToLimit);
@@ -3388,10 +4197,10 @@ private synchronized void animateTo(@Nullable List newItems) {
if (DEBUG)
Log.v(TAG, "NotifyDataSetChanged! oldSize=" + getItemCount() + " newSize=" + newItems.size() + " limit=" + mAnimateToLimit);
mTempItems = newItems;
- notifications.add(new Notification(-1, 0));
+ mNotifications.add(new Notification(-1, 0));
}
//Execute All notifications if filter was Synchronous!
- if (mFilterAsyncTask == null) executeNotifications();
+ if (mFilterAsyncTask == null) executeNotifications(payloadChange);
}
/**
@@ -3400,25 +4209,43 @@ private synchronized void animateTo(@Nullable List newItems) {
* @since 5.0.0-b1
*/
private void applyAndAnimateRemovals(List from, List newItems) {
- //Using Hash for performance
- hashItems = new HashSet<>(newItems);
- int out = 0;
+ // This avoids the call indexOf() later on: newItems.get(newItems.indexOf(item)));
+ // and use the hash for the get
+ Map existingItems = null;
+ if (notifyChangeOfUnfilteredItems) {
+ // Using Hash for performance
+ mHashItems = new HashSet<>(from);
+ existingItems = new HashMap<>();
+ for (int i = 0; i < newItems.size(); i++) {
+ if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled()) return;
+ final T item = newItems.get(i);
+ // Save the index of this new item
+ if (mHashItems.contains(item)) existingItems.put(item, i);
+ }
+ }
+ // Using Hash for performance
+ mHashItems = new HashSet<>(newItems);
+ int out = 0, mod = 0;
for (int i = from.size() - 1; i >= 0; i--) {
if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled()) return;
final T item = from.get(i);
- if (!hashItems.contains(item) && (!isHeader(item) || (isHeader(item) && headersShown))) {
+ if (!mHashItems.contains(item)) {
//if (DEBUG) Log.v(TAG, "calculateRemovals remove position=" + i + " item=" + item + " searchText=" + mSearchText);
from.remove(i);
- notifications.add(new Notification(i, Notification.REMOVE));
+ mNotifications.add(new Notification(i, Notification.REMOVE));
out++;
} else if (notifyChangeOfUnfilteredItems) {
- from.set(i, item);
- notifications.add(new Notification(i, Notification.CHANGE));
- //if (DEBUG) Log.v(TAG, "calculateRemovals keep position=" + i + " item=" + item + " searchText=" + mSearchText);
+ from.set(i, newItems.get(existingItems.get(item)));
+ mNotifications.add(new Notification(i, Notification.CHANGE));
+ mod++;
+ //if (DEBUG) Log.v(TAG, "calculateAdditions keep position=" + i + " item=" + item + " searchText=" + mSearchText);
}
}
- hashItems = null;
- if (DEBUG) Log.v(TAG, "calculateRemovals total out=" + out);
+ mHashItems = null;
+ if (DEBUG) {
+ Log.v(TAG, "calculateRemovals total out=" + out);
+ Log.v(TAG, "calculateModifications total mod=" + mod);
+ }
}
/**
@@ -3427,26 +4254,26 @@ private void applyAndAnimateRemovals(List from, List newItems) {
* @since 5.0.0-b1
*/
private void applyAndAnimateAdditions(List from, List newItems) {
- //Using Hash for performance
- hashItems = new HashSet<>(from);
+ // Using Hash for performance
+ mHashItems = new HashSet<>(from);
int in = 0;
for (int i = 0; i < newItems.size(); i++) {
if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled()) return;
final T item = newItems.get(i);
- if (!hashItems.contains(item)) {
- //if (DEBUG) Log.v(TAG, "calculateAdditions add position=" + i + " item=" + item + " searchText=" + mSearchText);
+ if (!mHashItems.contains(item)) {
+ // if (DEBUG) Log.v(TAG, "calculateAdditions add position=" + i + " item=" + item + " searchText=" + mSearchText);
if (notifyMoveOfFilteredItems) {
- //We add always at the end to animate moved items at the missing position
+ // We add always at the end to animate moved items at the missing position
from.add(item);
- notifications.add(new Notification(from.size(), Notification.ADD));
+ mNotifications.add(new Notification(from.size(), Notification.ADD));
} else {
from.add(i, item);
- notifications.add(new Notification(i, Notification.ADD));
+ mNotifications.add(new Notification(i, Notification.ADD));
}
in++;
}
}
- hashItems = null;
+ mHashItems = null;
if (DEBUG) Log.v(TAG, "calculateAdditions total new=" + in);
}
@@ -3463,49 +4290,80 @@ private void applyAndAnimateMovedItems(List from, List newItems) {
final T item = newItems.get(toPosition);
final int fromPosition = from.indexOf(item);
if (fromPosition >= 0 && fromPosition != toPosition) {
- //if (DEBUG) Log.v(TAG, "calculateMovedItems fromPosition=" + fromPosition + " toPosition=" + toPosition + " searchText=" + mSearchText);
+ // if (DEBUG) Log.v(TAG, "calculateMovedItems fromPosition=" + fromPosition + " toPosition=" + toPosition + " searchText=" + mSearchText);
T movedItem = from.remove(fromPosition);
if (toPosition < from.size()) from.add(toPosition, movedItem);
else from.add(movedItem);
- notifications.add(new Notification(fromPosition, toPosition, Notification.MOVE));
+ mNotifications.add(new Notification(fromPosition, toPosition, Notification.MOVE));
move++;
}
}
if (DEBUG) Log.v(TAG, "calculateMovedItems total move=" + move);
}
- private synchronized void executeNotifications() {
- if (DEBUG) Log.i(TAG, "Performing " + notifications.size() + " notifications");
- mItems = mTempItems;// Update mItems in the UI Thread
- setAnimate(false);//Disable scroll animation
- for (Notification notification : notifications) {
- switch (notification.operation) {
- case Notification.ADD:
- notifyItemInserted(notification.position);
- break;
- case Notification.CHANGE:
- notifyItemChanged(notification.position, Payload.FILTER);
- break;
- case Notification.REMOVE:
- notifyItemRemoved(notification.position);
- break;
- case Notification.MOVE:
- notifyItemMoved(notification.fromPosition, notification.position);
- break;
- default:
- if (DEBUG) Log.w(TAG, "notifyDataSetChanged!");
- notifyDataSetChanged();
- break;
+ private synchronized void executeNotifications(Payload payloadChange) {
+ if (diffResult != null) {
+ if (DEBUG) Log.i(TAG, "Dispatching notifications");
+ mItems = diffUtilCallback.getNewItems(); //Update mItems in the UI Thread
+ diffResult.dispatchUpdatesTo(this);
+ diffResult = null;
+ } else {
+ if (DEBUG) Log.i(TAG, "Performing " + mNotifications.size() + " notifications");
+ mItems = mTempItems; //Update mItems in the UI Thread
+ setScrollAnimate(false); //Disable scroll animation
+ for (Notification notification : mNotifications) {
+ switch (notification.operation) {
+ case Notification.ADD:
+ notifyItemInserted(notification.position);
+ break;
+ case Notification.CHANGE:
+ notifyItemChanged(notification.position, payloadChange);
+ break;
+ case Notification.REMOVE:
+ notifyItemRemoved(notification.position);
+ break;
+ case Notification.MOVE:
+ notifyItemMoved(notification.fromPosition, notification.position);
+ break;
+ default:
+ if (DEBUG) Log.w(TAG, "notifyDataSetChanged!");
+ notifyDataSetChanged();
+ break;
+ }
}
+ mTempItems = null;
+ mNotifications = null;
}
- mTempItems = null;
- notifications = null;
+ time = System.currentTimeMillis();
+ time = time - start;
+ if (DEBUG) Log.i(TAG, "Animate changes DONE in " + time + "ms");
+ }
+
+ /**
+ * @return the time (in ms) of the last update or filter operation.
+ */
+ public long getTime() {
+ return time;
}
/*---------------*/
/* TOUCH METHODS */
/*---------------*/
+ private void initializeItemTouchHelper() {
+ if (mItemTouchHelper == null) {
+ if (mRecyclerView == null) {
+ throw new IllegalStateException("RecyclerView cannot be null. Enabling LongPressDrag or Swipe must be done after the Adapter is added to the RecyclerView.");
+ }
+ if (mItemTouchHelperCallback == null) {
+ mItemTouchHelperCallback = new ItemTouchHelperCallback(this);
+ if (DEBUG) Log.i(TAG, "Initialized default ItemTouchHelperCallback");
+ }
+ mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback);
+ mItemTouchHelper.attachToRecyclerView(mRecyclerView);
+ }
+ }
+
/**
* Used by {@link FlexibleViewHolder#onTouch(View, MotionEvent)}
* to start Drag or Swipe when HandleView is touched.
@@ -3519,9 +4377,11 @@ public final ItemTouchHelper getItemTouchHelper() {
}
/**
- * Returns the customization of the ItemTouchHelperCallback.
+ * Returns the customization of the ItemTouchHelperCallback or the default if it wasn't set
+ * before.
*
* @return the ItemTouchHelperCallback instance already initialized
+ * @see #setItemTouchHelperCallback(ItemTouchHelperCallback)
* @since 5.0.0-b7
*/
public final ItemTouchHelperCallback getItemTouchHelperCallback() {
@@ -3529,6 +4389,23 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() {
return mItemTouchHelperCallback;
}
+ /**
+ * Sets a custom callback implementation for item touch.
+ * If called, Helper will be reinitialized.
+ * If not called, the default Helper will be used.
+ *
+ * @param itemTouchHelperCallback the custom callback implementation for item touch
+ * @return this Adapter, so the call can be chained
+ * @since 5.0.0-rc1
+ */
+ public final FlexibleAdapter setItemTouchHelperCallback(ItemTouchHelperCallback itemTouchHelperCallback) {
+ mItemTouchHelperCallback = itemTouchHelperCallback;
+ mItemTouchHelper = null;
+ initializeItemTouchHelper();
+ if (DEBUG) Log.i(TAG, "Initialized custom ItemTouchHelperCallback");
+ return this;
+ }
+
/**
* Returns whether ItemTouchHelper should start a drag and drop operation if an item is
* long pressed.
@@ -3536,6 +4413,7 @@ public final ItemTouchHelperCallback getItemTouchHelperCallback() {
*
* @return true if ItemTouchHelper should start dragging an item when it is long pressed,
* false otherwise. Default value is {@code false}.
+ * @see #setLongPressDragEnabled(boolean)
* @since 5.0.0-b1
*/
public final boolean isLongPressDragEnabled() {
@@ -3543,8 +4421,8 @@ public final boolean isLongPressDragEnabled() {
}
/**
- * Enable the Drag on LongPress on the entire ViewHolder.
- *
NOTE: This will skip LongClick on the view in order to handle the LongPress,
+ * Enable / Disable the Drag on LongPress on the entire ViewHolder.
+ *
Note: This will skip LongClick on the view in order to handle the LongPress,
* however the LongClick listener will be called if necessary in the new
* {@link FlexibleViewHolder#onActionStateChanged(int, int)}.
* Default value is {@code false}.
@@ -3555,24 +4433,28 @@ public final boolean isLongPressDragEnabled() {
*/
public final FlexibleAdapter setLongPressDragEnabled(boolean longPressDragEnabled) {
initializeItemTouchHelper();
+ if (DEBUG) Log.i(TAG, "Set longPressDragEnabled=" + longPressDragEnabled);
mItemTouchHelperCallback.setLongPressDragEnabled(longPressDragEnabled);
return this;
}
/**
- * Enabled by default.
- * To use, it is sufficient to set the HandleView by calling
- * {@link FlexibleViewHolder#setDragHandleView(View)}.
+ * Returns whether ItemTouchHelper should start a drag and drop operation by touching its
+ * handle.
+ * Default value is {@code false}.
+ * To use, it is sufficient to set the HandleView by calling
+ * {@link FlexibleViewHolder#setDragHandleView(View)}.
*
* @return true if active, false otherwise
+ * @see #setHandleDragEnabled(boolean)
* @since 5.0.0-b1
*/
public final boolean isHandleDragEnabled() {
- return handleDragEnabled;
+ return mItemTouchHelperCallback != null && mItemTouchHelperCallback.isHandleDragEnabled();
}
/**
- * Enable/Disable the drag with handle.
+ * Enable / Disable the drag of the itemView with a handle view.
* Default value is {@code false}.
*
* @param handleDragEnabled true to activate, false otherwise
@@ -3580,7 +4462,9 @@ public final boolean isHandleDragEnabled() {
* @since 5.0.0-b1
*/
public final FlexibleAdapter setHandleDragEnabled(boolean handleDragEnabled) {
- this.handleDragEnabled = handleDragEnabled;
+ initializeItemTouchHelper();
+ if (DEBUG) Log.i(TAG, "Set handleDragEnabled=" + handleDragEnabled);
+ this.mItemTouchHelperCallback.setHandleDragEnabled(handleDragEnabled);
return this;
}
@@ -3591,6 +4475,7 @@ public final FlexibleAdapter setHandleDragEnabled(boolean handleDragEnabled) {
*
* @return true if ItemTouchHelper should start swiping an item when user swipes a pointer
* over the View, false otherwise. Default value is {@code false}.
+ * @see #setSwipeEnabled(boolean)
* @since 5.0.0-b1
*/
public final boolean isSwipeEnabled() {
@@ -3606,6 +4491,7 @@ public final boolean isSwipeEnabled() {
* @since 5.0.0-b1
*/
public final FlexibleAdapter setSwipeEnabled(boolean swipeEnabled) {
+ if (DEBUG) Log.i(TAG, "Set swipeEnabled=" + swipeEnabled);
initializeItemTouchHelper();
mItemTouchHelperCallback.setSwipeEnabled(swipeEnabled);
return this;
@@ -3640,26 +4526,25 @@ public void moveItem(int fromPosition, int toPosition) {
public void moveItem(int fromPosition, int toPosition, @Nullable Object payload) {
if (DEBUG)
Log.v(TAG, "moveItem fromPosition=" + fromPosition + " toPosition=" + toPosition);
- //Preserve selection
+ // Preserve selection
if ((isSelected(fromPosition))) {
removeSelection(fromPosition);
addSelection(toPosition);
}
T item = mItems.get(fromPosition);
- //Preserve expanded status and Collapse expandable
+ // Preserve expanded status and Collapse expandable
boolean expanded = isExpanded(item);
if (expanded) collapse(toPosition);
- //Move item!
+ // Move item!
mItems.remove(fromPosition);
- if (toPosition < getItemCount()) mItems.add(toPosition, item);
- else mItems.add(item);
+ performInsert(toPosition, Collections.singletonList(item), false);
notifyItemMoved(fromPosition, toPosition);
if (payload != null) notifyItemChanged(toPosition, payload);
- //Eventually display the new Header
+ // Eventually display the new Header
if (headersShown) {
showHeaderOf(toPosition, item, false);
}
- //Restore original expanded status
+ // Restore original expanded status
if (expanded) expand(toPosition);
}
@@ -3682,12 +4567,12 @@ public void swapItems(List list, int fromPosition, int toPosition) {
toPosition + " [selected? " + isSelected(toPosition) + "]");
}
- //Collapse expandable before swapping (otherwise items are mixed badly)
-// if (fromPosition < toPosition && isExpandable(getItem(fromPosition)) && isExpanded(toPosition)) {
-// collapse(toPosition);
-// }
+ // Collapse expandable before swapping (otherwise items are mixed badly)
+ if (fromPosition < toPosition && isExpandable(getItem(fromPosition)) && isExpanded(toPosition)) {
+ collapse(toPosition);
+ }
- //Perform item swap (for all LayoutManagers)
+ // Perform item swap (for all LayoutManagers)
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
if (DEBUG) Log.v(TAG, "swapItems from=" + i + " to=" + (i + 1));
@@ -3703,24 +4588,24 @@ public void swapItems(List list, int fromPosition, int toPosition) {
}
notifyItemMoved(fromPosition, toPosition);
- //Header swap linkage
+ // Header swap linkage
if (headersShown) {
- //Situation AFTER items have been swapped, items are inverted!
+ // Situation AFTER items have been swapped, items are inverted!
T fromItem = getItem(toPosition);
T toItem = getItem(fromPosition);
int oldPosition, newPosition;
if (toItem instanceof IHeader && fromItem instanceof IHeader) {
if (fromPosition < toPosition) {
- //Dragging down fromHeader
- //Auto-linkage all section-items with new header
+ // Dragging down fromHeader
+ // Auto-linkage all section-items with new header
IHeader header = (IHeader) fromItem;
List items = getSectionItems(header);
for (ISectionable sectionable : items) {
linkHeaderTo((T) sectionable, header, Payload.LINK);
}
} else {
- //Dragging up fromHeader
- //Auto-linkage all section-items with new header
+ // Dragging up fromHeader
+ // Auto-linkage all section-items with new header
IHeader header = (IHeader) toItem;
List items = getSectionItems(header);
for (ISectionable sectionable : items) {
@@ -3728,27 +4613,27 @@ public void swapItems(List list, int fromPosition, int toPosition) {
}
}
} else if (toItem instanceof IHeader) {
- //A Header is being swapped up
- //Else a Header is being swapped down
+ // A Header is being swapped up
+ // Else a Header is being swapped down
oldPosition = fromPosition < toPosition ? toPosition + 1 : toPosition;
newPosition = fromPosition < toPosition ? toPosition : fromPosition + 1;
- //Swap header linkage
+ // Swap header linkage
linkHeaderTo(getItem(oldPosition), getSectionHeader(oldPosition), Payload.LINK);
linkHeaderTo(getItem(newPosition), (IHeader) toItem, Payload.LINK);
} else if (fromItem instanceof IHeader) {
- //A Header is being dragged down
- //Else a Header is being dragged up
+ // A Header is being dragged down
+ // Else a Header is being dragged up
oldPosition = fromPosition < toPosition ? fromPosition : fromPosition + 1;
newPosition = fromPosition < toPosition ? toPosition + 1 : fromPosition;
- //Swap header linkage
+ // Swap header linkage
linkHeaderTo(getItem(oldPosition), getSectionHeader(oldPosition), Payload.LINK);
linkHeaderTo(getItem(newPosition), (IHeader) fromItem, Payload.LINK);
} else {
- //A Header receives the toItem
- //Else a Header receives the fromItem
+ // A Header receives the toItem
+ // Else a Header receives the fromItem
oldPosition = fromPosition < toPosition ? toPosition : fromPosition;
newPosition = fromPosition < toPosition ? fromPosition : toPosition;
- //Swap header linkage
+ // Swap header linkage
T oldItem = getItem(oldPosition);
IHeader header = getHeaderOf(oldItem);
if (header != null) {
@@ -3783,8 +4668,9 @@ else if (mItemSwipeListener != null) {
*/
@Override
public boolean shouldMove(int fromPosition, int toPosition) {
- return (mItemMoveListener == null || mItemMoveListener.shouldMoveItem(fromPosition, toPosition));// &&
- //!(isExpandable(getItem(fromPosition)) && getExpandableOf(toPosition) != null);
+ T toItem = getItem(toPosition);
+ return !(mScrollableHeaders.contains(toItem) || mScrollableFooters.contains(toItem)) &&
+ (mItemMoveListener == null || mItemMoveListener.shouldMoveItem(fromPosition, toPosition));
}
/**
@@ -3811,23 +4697,12 @@ public boolean onItemMove(int fromPosition, int toPosition) {
@Override
@CallSuper
public void onItemSwiped(int position, int direction) {
- //Delegate actions to the user
+ // Delegate actions to the user
if (mItemSwipeListener != null) {
mItemSwipeListener.onItemSwipe(position, direction);
}
}
- private void initializeItemTouchHelper() {
- if (mItemTouchHelper == null) {
- if (mRecyclerView == null) {
- throw new IllegalStateException("RecyclerView cannot be null. Enabling LongPressDrag or Swipe must be done after the Adapter is added to the RecyclerView.");
- }
- mItemTouchHelperCallback = new ItemTouchHelperCallback(this);
- mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback);
- mItemTouchHelper.attachToRecyclerView(mRecyclerView);
- }
- }
-
/*------------------------*/
/* OTHERS PRIVATE METHODS */
/*------------------------*/
@@ -3842,7 +4717,7 @@ private void mapViewTypeFrom(T item) {
if (item != null && !mTypeInstances.containsKey(item.getLayoutRes())) {
mTypeInstances.put(item.getLayoutRes(), item);
if (DEBUG)
- Log.i(TAG, "Mapped viewType " + item.getLayoutRes() + " from " + item.getClass().getSimpleName());
+ Log.i(TAG, "Mapped viewType " + item.getLayoutRes() + " from " + getClassName(item));
}
}
@@ -3863,7 +4738,7 @@ private T getViewTypeInstance(int viewType) {
*/
private RestoreInfo getPendingRemovedItem(T item) {
for (RestoreInfo restoreInfo : mRestoreList) {
- //refPosition >= 0 means that position has been calculated and restore is ongoing
+ // refPosition >= 0 means that position has been calculated and restore is ongoing
if (restoreInfo.item.equals(item) && restoreInfo.refPosition < 0) return restoreInfo;
}
return null;
@@ -3893,15 +4768,15 @@ private int createRestoreSubItemInfo(IExpandable expandable, T item, @Nullable O
* @since 5.0.0-b1
*/
private void createRestoreItemInfo(int position, T item, @Nullable Object payload) {
- //Collapse Parent before removal if it is expanded!
+ // Collapse Parent before removal if it is expanded!
if (isExpanded(item))
collapse(position);
item.setHidden(true);
- //Get the reference of the previous item (getItem returns null if outOfBounds)
- //If null, it will be restored at position = 0
+ // Get the reference of the previous item (getItem returns null if outOfBounds)
+ // If null, it will be restored at position = 0
T refItem = getItem(position - 1);
if (refItem != null) {
- //Check if the refItem is a child of an Expanded parent, take the parent!
+ // Check if the refItem is a child of an Expanded parent, take the parent!
IExpandable expandable = getExpandableOf(refItem);
if (expandable != null) refItem = (T) expandable;
}
@@ -3921,7 +4796,7 @@ private List getExpandableList(IExpandable expandable) {
if (expandable != null && hasSubItems(expandable)) {
List allSubItems = expandable.getSubItems();
for (T subItem : allSubItems) {
- //Pick up only no hidden items (doesn't get into account the filtered items)
+ // Pick up only no hidden items (doesn't get into account the filtered items)
if (!subItem.isHidden()) subItems.add(subItem);
}
}
@@ -3938,15 +4813,21 @@ private List getExpandableList(IExpandable expandable) {
*/
private boolean hasSubItemsSelected(int startPosition, List subItems) {
for (T subItem : subItems) {
- if (isSelected(startPosition + 1) ||
- (isExpandable(subItem) && hasSubItemsSelected(startPosition + 1, getExpandableList((IExpandable) subItem))))
+ if (isSelected(++startPosition) ||
+ (isExpanded(subItem) && hasSubItemsSelected(startPosition, getExpandableList((IExpandable) subItem))))
return true;
}
return false;
}
+ private void performScroll(final int position) {
+ if (mRecyclerView != null) {
+ mRecyclerView.smoothScrollToPosition(Math.min(Math.max(0, position), getItemCount() - 1));
+ }
+ }
+
private void autoScrollWithDelay(final int position, final int subItemsCount, final long delay) {
- //Must be delayed to give time at RecyclerView to recalculate positions after an automatic collapse
+ // Must be delayed to give time at RecyclerView to recalculate positions after an automatic collapse
new Handler(Looper.getMainLooper(), new Handler.Callback() {
public boolean handleMessage(Message message) {
int firstVisibleItem = Utils.findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
@@ -3958,16 +4839,16 @@ public boolean handleMessage(Message message) {
int scrollMax = position - firstVisibleItem;
int scrollMin = Math.max(0, position + subItemsCount - lastVisibleItem);
int scrollBy = Math.min(scrollMax, scrollMin);
- int spanCount = getSpanCount(mRecyclerView.getLayoutManager());
+ int spanCount = Utils.getSpanCount(mRecyclerView.getLayoutManager());
if (spanCount > 1) {
scrollBy = scrollBy % spanCount + spanCount;
}
int scrollTo = firstVisibleItem + scrollBy;
// if (DEBUG)
// Log.v(TAG, "autoScroll scrollMin=" + scrollMin + " scrollMax=" + scrollMax + " scrollBy=" + scrollBy + " scrollTo=" + scrollTo);
- mRecyclerView.smoothScrollToPosition(scrollTo);
+ performScroll(scrollTo);
} else if (position < firstVisibleItem) {
- mRecyclerView.smoothScrollToPosition(position);
+ performScroll(position);
}
return true;
}
@@ -3977,16 +4858,29 @@ public boolean handleMessage(Message message) {
private void adjustSelected(int startPosition, int itemCount) {
List selectedPositions = getSelectedPositions();
boolean adjusted = false;
+ String diff = "";
+ if (itemCount > 0) {
+ // Reverse sorting is necessary because using Set removes duplicates
+ // during adjusting, so we scan backward.
+ Collections.sort(selectedPositions, new Comparator() {
+ @Override
+ public int compare(Integer lhs, Integer rhs) {
+ return rhs - lhs;
+ }
+ });
+ diff = "+";
+ }
for (Integer position : selectedPositions) {
if (position >= startPosition) {
- if (DEBUG)
- Log.v(TAG, "Adjust Selected position " + position + " to " + Math.max(position + itemCount, startPosition));
+// if (DEBUG)
+// Log.v(TAG, "Adjust Selected position " + position + " to " + Math.max(position + itemCount, startPosition));
removeSelection(position);
- addSelection(Math.max(position + itemCount, startPosition));
+ addAdjustedSelection(Math.max(position + itemCount, startPosition));
adjusted = true;
}
}
- if (DEBUG && adjusted) Log.v(TAG, "AdjustedSelected=" + getSelectedPositions());
+ if (DEBUG && adjusted)
+ Log.v(TAG, "AdjustedSelected(" + diff + itemCount + ")=" + getSelectedPositions());
}
/*----------------*/
@@ -4001,16 +4895,21 @@ private void adjustSelected(int startPosition, int itemCount) {
*/
public void onSaveInstanceState(Bundle outState) {
if (outState != null) {
- //Save selection state
+ // Save selection state
+ if (mScrollableHeaders.size() > 0) {
+ // We need to rollback the added item positions if headers were added lately
+ adjustSelected(0, -mScrollableHeaders.size());
+ }
super.onSaveInstanceState(outState);
- //Save selection coherence
+ // Save selection coherence
outState.putBoolean(EXTRA_CHILD, this.childSelected);
outState.putBoolean(EXTRA_PARENT, this.parentSelected);
- outState.putInt(EXTRA_LEVEL, this.selectedLevel);
- //Current filter. Old text is not saved otherwise animateTo() cannot be called
+ outState.putInt(EXTRA_LEVEL, this.mSelectedLevel);
+ // Current filter. Old text is not saved otherwise animateTo() cannot be called
outState.putString(EXTRA_SEARCH, this.mSearchText);
- //Save headers shown status
+ // Save headers shown status
outState.putBoolean(EXTRA_HEADERS, this.headersShown);
+ outState.putBoolean(EXTRA_STICKY, areHeadersSticky());
}
}
@@ -4022,7 +4921,7 @@ public void onSaveInstanceState(Bundle outState) {
*/
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
- //Restore headers shown status
+ // Restore headers shown status
boolean headersShown = savedInstanceState.getBoolean(EXTRA_HEADERS);
if (!headersShown) {
hideAllHeaders();
@@ -4030,13 +4929,20 @@ public void onRestoreInstanceState(Bundle savedInstanceState) {
showAllHeadersWithReset(true);
}
this.headersShown = headersShown;
- //Restore selection state
+ if (savedInstanceState.getBoolean(EXTRA_STICKY) && !areHeadersSticky()) {
+ setStickyHeaders(true);
+ }
+ // Restore selection state
super.onRestoreInstanceState(savedInstanceState);
- //Restore selection coherence
+ if (mScrollableHeaders.size() > 0) {
+ // We need to restore the added item positions if headers were added early
+ adjustSelected(0, mScrollableHeaders.size());
+ }
+ // Restore selection coherence
this.parentSelected = savedInstanceState.getBoolean(EXTRA_PARENT);
this.childSelected = savedInstanceState.getBoolean(EXTRA_CHILD);
- this.selectedLevel = savedInstanceState.getInt(EXTRA_LEVEL);
- //Current filter (old text must not be saved)
+ this.mSelectedLevel = savedInstanceState.getInt(EXTRA_LEVEL);
+ // Current filter (old text must not be saved)
this.mSearchText = savedInstanceState.getString(EXTRA_SEARCH);
}
}
@@ -4050,9 +4956,12 @@ public void onRestoreInstanceState(Bundle savedInstanceState) {
*/
public interface OnUpdateListener {
/**
- * Called at startup and every time an item is inserted, removed or filtered.
+ * Called at startup and every time a main item is inserted, removed or filtered.
+ * Note: Having any Scrollable Headers/Footers visible, the {@code size}
+ * will represents only the main items.
*
- * @param size the current number of items in the adapter, result of {@link #getItemCount()}
+ * @param size the current number of main items in the adapter, result of
+ * {@link FlexibleAdapter#getMainItemCount()}
* @since 5.0.0-b1
*/
void onUpdateEmptyView(int size);
@@ -4065,8 +4974,8 @@ public interface OnDeleteCompleteListener {
/**
* Called when Undo timeout is over and removal must be committed in the user Database.
* Due to Java Generic, it's too complicated and not
- * well manageable if we pass the List<T> object.
- * To get deleted items, use {@link #getDeletedItems()} from the
+ * well manageable if we pass the List<T> object.
+ * To get deleted items, use {@link FlexibleAdapter#getDeletedItems()} from the
* implementation of this method.
*
* @since 5.0.0-b1
@@ -4080,12 +4989,14 @@ public interface OnDeleteCompleteListener {
public interface OnItemClickListener {
/**
* Called when single tap occurs.
- * Delegates the click event to the listener and checks if selection MODE if
- * SINGLE or MULTI is enabled in order to activate the ItemView.
+ * This method receives the click event generated from the itemView to check if one
+ * of the selection mode ({@code SINGLE or MULTI}) is enabled in order to activate the
+ * itemView.
* For Expandable Views it will toggle the Expansion if configured so.
*
* @param position the adapter position of the item clicked
- * @return true if the click should activate the ItemView, false for no change.
+ * @return true if the click should activate the itemView according to the selection mode,
+ * false for no change to the itemView.
* @since 5.0.0-b1
*/
boolean onItemClick(int position);
@@ -4097,9 +5008,8 @@ public interface OnItemClickListener {
public interface OnItemLongClickListener {
/**
* Called when long tap occurs.
- * This method always calls
- * {@link FlexibleViewHolder#toggleActivation}
- * after listener event is consumed in order to activate the ItemView.
+ * This method always calls {@link FlexibleViewHolder#toggleActivation()}
+ * after the listener event is consumed in order to activate the itemView.
* For Expandable Views it will collapse the View if configured so.
*
* @param position the adapter position of the item clicked
@@ -4113,8 +5023,8 @@ public interface OnItemLongClickListener {
*/
public interface OnActionStateListener {
/**
- * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped
- * or when has been released.
+ * Called when the {@link ItemTouchHelper} first registers an item as being moved
+ * or swiped or when has been released.
* Override this method to receive touch events with its state.
*
* @param viewHolder the viewHolder touched
@@ -4138,7 +5048,7 @@ public interface OnItemMoveListener extends OnActionStateListener {
* @param toPosition the potential resolved position of the swapped item
* @return return true if the items can swap ({@code onItemMove()} will be called),
* false otherwise (nothing happens)
- * @see #onItemMove(int, int)
+ * @see FlexibleAdapter#onItemMove(int, int)
* @since 5.0.0-b8
*/
boolean shouldMoveItem(int fromPosition, int toPosition);
@@ -4151,7 +5061,7 @@ public interface OnItemMoveListener extends OnActionStateListener {
*
* @param fromPosition the start position of the moved item
* @param toPosition the resolved position of the moved item
- * @see #shouldMoveItem(int, int)
+ * @see FlexibleAdapter#shouldMove(int, int)
* @since 5.0.0-b1
*/
void onItemMove(int fromPosition, int toPosition);
@@ -4162,7 +5072,7 @@ public interface OnItemMoveListener extends OnActionStateListener {
*/
public interface OnItemSwipeListener extends OnActionStateListener {
/**
- * Called when swiping ended its animation and Item is not visible anymore.
+ * Called when swiping ended its animation and item is not visible anymore.
*
* @param position the position of the item swiped
* @param direction the direction to which the ViewHolder is swiped, one of:
@@ -4191,13 +5101,33 @@ public interface OnStickyHeaderChangeListener {
/**
* @since 22/04/2016
*/
- public interface EndlessScrollListener {
+ public interface EndlessScrollListener {
+
+ /**
+ * No more data to load.
+ * This method is called if any limit is reached (targetCount or pageSize
+ * must be set) AND if new data is temporary unavailable (ex. no connection or no
+ * new updates remotely). If no new data, a {@link FlexibleAdapter#notifyItemChanged(int, Object)}
+ * with a payload {@link Payload#NO_MORE_LOAD} is triggered on the progressItem.
+ *
+ * @param newItemsSize the last size of the new items loaded
+ * @see FlexibleAdapter#setEndlessTargetCount(int)
+ * @see FlexibleAdapter#setEndlessPageSize(int)
+ * @since 5.0.0-rc1
+ */
+ void noMoreLoad(int newItemsSize);
+
/**
* Loads more data.
+ * Use {@code lastPosition} and {@code currentPage} to know what to load next.
+ * {@code lastPosition} is the count of the main items without Scrollable Headers.
*
+ * @param lastPosition the position of the last main item in the adapter
+ * @param currentPage the current page
* @since 5.0.0-b6
+ *
5.0.0-rc1 added {@code lastPosition} and {@code currentPage} as parameters
*/
- void onLoadMore();
+ void onLoadMore(int lastPosition, int currentPage);
}
/**
@@ -4206,8 +5136,8 @@ public interface EndlessScrollListener {
private class AdapterDataObserver extends RecyclerView.AdapterDataObserver {
private void adjustPositions(int positionStart, int itemCount) {
- if (!filtering) {//Filtering has multiple insert and removal, we skip this process
- if (adjustSelected)//Don't, if remove range / restore
+ if (!filtering) { //Filtering has multiple insert and removal, we skip this process
+ if (adjustSelected) //Don't, if remove range / restore
adjustSelected(positionStart, itemCount);
adjustSelected = true;
}
@@ -4263,7 +5193,7 @@ public RestoreInfo(T refItem, T item, Object payload) {
}
public RestoreInfo(T refItem, T item, int relativePosition, Object payload) {
- this.refItem = refItem;//This can be an Expandable or a Header
+ this.refItem = refItem; //This can be an Expandable or a Header
this.item = item;
this.relativePosition = relativePosition;
this.payload = payload;
@@ -4278,7 +5208,7 @@ public int getRestorePosition(boolean isChild) {
}
T item = getItem(refPosition);
if (isChild && isExpandable(item)) {
- //Assert the expandable children are collapsed
+ // Assert the expandable children are collapsed
recursiveCollapse(refPosition, getCurrentChildren((IExpandable) item), 0);
} else if (isExpanded(item) && !isChild) {
refPosition += getExpandableList((IExpandable) item).size() + 1;
@@ -4344,10 +5274,12 @@ protected void onPreExecute() {
@Override
protected Void doInBackground(Void... params) {
+ start = System.currentTimeMillis();
switch (what) {
case UPDATE:
if (DEBUG) Log.d(TAG, "doInBackground - started UPDATE");
- animateTo(newItems);
+ animateDiff(newItems, Payload.CHANGE);
+ //animateTo(newItems, Payload.CHANGE);
if (DEBUG) Log.d(TAG, "doInBackground - ended UPDATE");
break;
case FILTER:
@@ -4361,51 +5293,211 @@ protected Void doInBackground(Void... params) {
@Override
protected void onPostExecute(Void result) {
- //Notify all the changes
- executeNotifications();
- //Execute post data
- switch (what) {
- case UPDATE:
- postUpdate(false);
- break;
- case FILTER:
- postFilter();
- break;
+ if (diffResult != null || mNotifications != null) {
+ //Execute post data
+ switch (what) {
+ case UPDATE:
+ // Notify all the changes
+ executeNotifications(Payload.CHANGE);
+ postUpdate(false);
+ break;
+ case FILTER:
+ // Notify all the changes
+ executeNotifications(Payload.FILTER);
+ postFilter();
+ break;
+ }
}
mFilterAsyncTask = null;
}
}
/**
- * @param init true to skip all notifications and instant reset by calling notifyDataSetChanged
+ * @param init true to skip all notifications and instant refresh the list by calling
+ * {@link #notifyDataSetChanged()}
*/
private void postUpdate(boolean init) {
- //Show headers and expanded items if Data Set not empty
+ // Show headers and expanded items if Data Set not empty
if (getItemCount() > 0) {
expandItemsAtStartUp();
if (headersShown) {
showAllHeadersWithReset(init);
}
}
- //Execute instant reset on init
+ // Execute instant reset on init
if (init) {
- if (DEBUG) Log.w(TAG, "notifyDataSetChanged!");
+ if (DEBUG) Log.w(TAG, "updateDataSet with notifyDataSetChanged!");
notifyDataSetChanged();
}
- //Update empty view
+ // Perform user code
+ onPostUpdate();
+ // Update empty view
if (mUpdateListener != null) {
- mUpdateListener.onUpdateEmptyView(getItemCount());
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
}
}
+ /**
+ * This method is called after the execution of Async Update and before the call to the
+ * {@link OnUpdateListener#onUpdateEmptyView(int)}.
+ *
+ * @see #updateDataSet(List, boolean)
+ */
+ protected void onPostUpdate() {
+ // Dedicated for user implementation
+ }
+
private void postFilter() {
- //Restore headers if necessary
+ // Restore headers if necessary
if (headersShown && !hasSearchText()) {
showAllHeadersWithReset(false);
}
- //Call listener to update EmptyView, assuming the filter always made a change
+ // Perform user code
+ onPostFilter();
+ // Call listener to update EmptyView, assuming the filter always made a change
if (mUpdateListener != null)
- mUpdateListener.onUpdateEmptyView(getItemCount());
+ mUpdateListener.onUpdateEmptyView(getMainItemCount());
+ }
+
+ /**
+ * This method is called after the execution of Async Filter and before the call to the
+ * {@link OnUpdateListener#onUpdateEmptyView(int)}.
+ *
+ * @see #filterItems(List)
+ */
+ protected void onPostFilter() {
+ // Dedicated for user implementation
+ }
+
+ /**
+ * Handler callback for delayed actions.
+ * You can use and override this Callback, current values used by the Adapter:
+ * 0 = async call for updateDataSet.
+ *
1 = async call for filterItems, optionally delayed.
+ *
2 = deleteConfirmed when Undo timeout is over.
+ *
8 = hide the progress item from the list, optionally delayed.
+ * Note: numbers 0-9 are reserved for the Adapter, use others.
+ *
+ * @since 5.0.0-rc1
+ */
+ public class HandlerCallback implements Handler.Callback {
+
+ @CallSuper
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case UPDATE: //updateDataSet OR
+ case FILTER: //filterItems
+ if (mFilterAsyncTask != null) mFilterAsyncTask.cancel(true);
+ mFilterAsyncTask = new FilterAsyncTask(message.what, (List) message.obj);
+ mFilterAsyncTask.execute();
+ return true;
+ case CONFIRM_DELETE: //confirm delete
+ OnDeleteCompleteListener listener = (OnDeleteCompleteListener) message.obj;
+ if (listener != null) listener.onDeleteConfirmed();
+ emptyBin();
+ return true;
+ case LOAD_MORE_COMPLETE: //hide progress item
+ hideProgressItem();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * The old and new lists are available as:
+ * - {@code protected List oldItems;}
+ *
- {@code protected List newItems;}
+ *
+ * @deprecated DiffUtil is slower than the internal implementation, you can use it until
+ * final release.
+ */
+ @Deprecated
+ public static class DiffUtilCallback extends DiffUtil.Callback {
+
+ protected List oldItems;
+ protected List newItems;
+
+ public final void setItems(List oldItems, List newItems) {
+ this.oldItems = oldItems;
+ this.newItems = newItems;
+ }
+
+ public final List getNewItems() {
+ return newItems;
+ }
+
+ @Override
+ public final int getOldListSize() {
+ return oldItems.size();
+ }
+
+ @Override
+ public final int getNewListSize() {
+ return newItems.size();
+ }
+
+ /**
+ * Called by the DiffUtil to decide whether two object represent the same item.
+ *
+ * For example, if your items have unique ids, this method should check their id equality.
+ *
+ * @param oldItemPosition The position of the item in the old list
+ * @param newItemPosition The position of the item in the new list
+ * @return True if the two items represent the same object or false if they are different.
+ */
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldItems.get(oldItemPosition);
+ T newItem = newItems.get(newItemPosition);
+ return oldItem.equals(newItem);
+ }
+
+ /**
+ * Called by the DiffUtil when it wants to check whether two items have the same data.
+ * DiffUtil uses this information to detect if the contents of an item has changed.
+ *
+ * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
+ * so that you can change its behavior depending on your UI.
+ * For example, if you are using DiffUtil with a
+ * {@link RecyclerView.Adapter RecyclerView.Adapter}, you should
+ * return whether the items' visual representations are the same.
+ *
+ * This method is called only if {@link #areItemsTheSame(int, int)} returns
+ * {@code true} for these items.
+ *
+ * @param oldItemPosition The position of the item in the old list
+ * @param newItemPosition The position of the item in the new list which replaces the
+ * oldItem
+ * @return True if the contents of the items are the same or false if they are different.
+ */
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return false;
+ }
+
+ /**
+ * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and
+ * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil
+ * calls this method to get a payload about the change.
+ *
+ * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
+ * particular field that changed in the item and your
+ * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+ * information to run the correct animation.
+ *
+ * Default implementation returns {@code null}.
+ *
+ * @param oldItemPosition The position of the item in the old list
+ * @param newItemPosition The position of the item in the new list
+ * @return A payload object that represents the change between the two items.
+ */
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ return Payload.CHANGE;
+ }
}
}
\ No newline at end of file
diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/Payload.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/Payload.java
index 058d5e6e..feef4913 100644
--- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/Payload.java
+++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/Payload.java
@@ -17,8 +17,8 @@
/**
* Payload occurs only for {@code notifyItemChanged()}.
- *
The value of this enumeration will be passed to the bind method to optimize the view binding
- * in order to update only the inner views interested for the change.
+ * The value of this enumeration will be provided to the bind method to optimize the view
+ * binding in order to update only the inner views interested for the change.
* You can still pass your own Object instead of one of these values.
*
* @author Davide Steduto
@@ -46,5 +46,9 @@ public enum Payload {
/** when items are notified due to Selecting All items / Clearing Selection) */
SELECTION,
/** when items are notified after a split */
- SPLIT
+ SPLIT,
+ /** when an item is expanded by the user */
+ EXPANDED,
+ /** when an item is collapsed by the user */
+ COLLAPSED
}
\ No newline at end of file
diff --git a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java
index b28a5691..f1c018c3 100644
--- a/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java
+++ b/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/SelectableAdapter.java
@@ -18,9 +18,8 @@
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
-import android.support.v7.widget.GridLayoutManager;
+import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.Log;
import android.view.View;
@@ -28,6 +27,8 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -64,7 +65,8 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter
public static final int MODE_IDLE = 0, MODE_SINGLE = 1, MODE_MULTI = 2;
/**
- * Annotation interface for selection modes.
+ * Annotation interface for selection modes:
+ * {@link #MODE_IDLE}, {@link #MODE_SINGLE}, {@link #MODE_MULTI}
*/
@IntDef({MODE_IDLE, MODE_SINGLE, MODE_MULTI})
@Retention(RetentionPolicy.SOURCE)
@@ -72,10 +74,17 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter
}
private Set mSelectedPositions;
+ private Set mBoundViewHolders;
private int mMode;
protected RecyclerView mRecyclerView;
protected FastScroller mFastScroller;
+ /**
+ * Flag when fast scrolling is active.
+ * Used to know if user is fast scrolling.
+ */
+ protected boolean isFastScroll = false;
+
/**
* ActionMode selection flag SelectAll.
* Used when user click on selectAll action button in ActionMode.
@@ -96,7 +105,9 @@ public abstract class SelectableAdapter extends RecyclerView.Adapter
* @since 1.0.0
*/
public SelectableAdapter() {
+ Log.i("FlexibleAdapter", "Running version " + BuildConfig.VERSION_NAME);
mSelectedPositions = new TreeSet<>();
+ mBoundViewHolders = new HashSet<>();
mMode = MODE_IDLE;
}
@@ -149,23 +160,6 @@ public RecyclerView getRecyclerView() {
return mRecyclerView;
}
- /**
- * Helper method to return the number of the columns (span count) of the given LayoutManager.
- * All Layouts are supported.
- *
- * @param layoutManager the layout manager to check
- * @return the span count
- * @since 5.0.0-b7
- */
- public static int getSpanCount(RecyclerView.LayoutManager layoutManager) {
- if (layoutManager instanceof GridLayoutManager) {
- return ((GridLayoutManager) layoutManager).getSpanCount();
- } else if (layoutManager instanceof StaggeredGridLayoutManager) {
- return ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
- }
- return 1;
- }
-
/**
* Sets the mode of the selection:
*